@chat-lab/ui 0.1.0-beta.14 → 0.1.0-beta.16

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 (35) hide show
  1. package/dist/components/Chatkit/index.cjs.map +1 -1
  2. package/dist/components/Chatkit/index.d.ts +1 -0
  3. package/dist/components/Chatkit/index.d.ts.map +1 -1
  4. package/dist/components/Chatkit/index.js.map +1 -1
  5. package/dist/components/assistant-ui/markdown-text.cjs +21 -2
  6. package/dist/components/assistant-ui/markdown-text.cjs.map +1 -1
  7. package/dist/components/assistant-ui/markdown-text.d.ts.map +1 -1
  8. package/dist/components/assistant-ui/markdown-text.js +21 -2
  9. package/dist/components/assistant-ui/markdown-text.js.map +1 -1
  10. package/dist/components/assistant-ui/shiki-highlighter.cjs +1 -1
  11. package/dist/components/assistant-ui/shiki-highlighter.cjs.map +1 -1
  12. package/dist/components/assistant-ui/shiki-highlighter.js +1 -1
  13. package/dist/components/assistant-ui/shiki-highlighter.js.map +1 -1
  14. package/dist/components/assistant-ui/thread.cjs +1 -1
  15. package/dist/components/assistant-ui/thread.cjs.map +1 -1
  16. package/dist/components/assistant-ui/thread.js +1 -1
  17. package/dist/components/assistant-ui/thread.js.map +1 -1
  18. package/dist/index.css +1 -1
  19. package/dist/packages/core/dist/index.cjs +2 -0
  20. package/dist/packages/core/dist/index.cjs.map +1 -1
  21. package/dist/packages/core/dist/index.js +2 -0
  22. package/dist/packages/core/dist/index.js.map +1 -1
  23. package/dist/utils/imageLoadingPlugin.cjs +73 -0
  24. package/dist/utils/imageLoadingPlugin.cjs.map +1 -0
  25. package/dist/utils/imageLoadingPlugin.d.ts +5 -0
  26. package/dist/utils/imageLoadingPlugin.d.ts.map +1 -0
  27. package/dist/utils/imageLoadingPlugin.js +71 -0
  28. package/dist/utils/imageLoadingPlugin.js.map +1 -0
  29. package/dist/utils/markdownCursorPlugin.cjs +65 -0
  30. package/dist/utils/markdownCursorPlugin.cjs.map +1 -0
  31. package/dist/utils/markdownCursorPlugin.d.ts +4 -0
  32. package/dist/utils/markdownCursorPlugin.d.ts.map +1 -0
  33. package/dist/utils/markdownCursorPlugin.js +63 -0
  34. package/dist/utils/markdownCursorPlugin.js.map +1 -0
  35. package/package.json +8 -1
@@ -0,0 +1,73 @@
1
+ 'use strict';
2
+
3
+ var index = require('../node_modules/.pnpm/unist-util-visit-parents@6.0.2/node_modules/unist-util-visit-parents/lib/index.cjs');
4
+
5
+ /**
6
+ * 匹配未闭合的图片语法
7
+ * 规则:
8
+ * 1. (?<!\\) : 前面没有反斜杠(非转义)
9
+ * 2. !\[ : 以 ![ 开头
10
+ * 3. [^\]]* : 中括号内非 ] 字符
11
+ * 4. \] : 中括号闭合
12
+ * 5. \( : 接左圆括号
13
+ * 6. [^)]*$ : 圆括号后直到字符串结束都不能有右括号
14
+ */
15
+ const INCOMPLETE_IMAGE_REGEX = /(?<!\\)!\[[^\]]*\]\([^)]*$/;
16
+ // 定义我们需要忽略的父级节点类型
17
+ const IGNORED_PARENTS = new Set(['code', 'inlineCode', 'link', 'image']);
18
+ const remarkLoadingImage = () => {
19
+ const transformer = (tree) => {
20
+ // 显式声明 node 为 Text 类型,ancestors 为 Node 数组
21
+ index.visitParents(tree, 'text', (node, ancestors) => {
22
+ const value = node.value;
23
+ // === 1. 环境检测 (Context Awareness) ===
24
+ // 检查祖先节点链中是否包含代码块、链接等不应该处理的类型
25
+ const isInsideIgnoredElement = ancestors.some((parent) => IGNORED_PARENTS.has(parent.type));
26
+ if (isInsideIgnoredElement) {
27
+ return;
28
+ }
29
+ // === 2. 正则匹配 ===
30
+ const match = value.match(INCOMPLETE_IMAGE_REGEX);
31
+ // 必须匹配,且匹配的内容必须位于字符串的**最末尾**
32
+ if (!match || match.index === undefined || match.index + match[0].length !== value.length) {
33
+ return;
34
+ }
35
+ // === 3. 节点替换逻辑 ===
36
+ // 保留未完成图片语法之前的正常文本
37
+ const cleanText = value.replace(INCOMPLETE_IMAGE_REGEX, '');
38
+ const newNodes = [];
39
+ // 如果有正常文本,先推入一个 Text 节点
40
+ if (cleanText) {
41
+ newNodes.push({ type: 'text', value: cleanText });
42
+ }
43
+ // 构造自定义的 Loading 占位节点
44
+ const loadingNode = {
45
+ type: 'imageLoading',
46
+ data: {
47
+ // 指示 rehype 将其渲染为 div 标签
48
+ hName: 'div',
49
+ hProperties: {
50
+ className: ['img-loading-placeholder'],
51
+ 'data-content': '正在生成图片...',
52
+ },
53
+ },
54
+ };
55
+ newNodes.push(loadingNode);
56
+ // === 4. 修改 AST 树 ===
57
+ // 获取直接父节点 (Parent 类型)
58
+ const parent = ancestors[ancestors.length - 1];
59
+ const index = parent.children.indexOf(node);
60
+ if (index !== -1) {
61
+ // 使用 splice 替换节点
62
+ parent.children.splice(index, 1, ...newNodes);
63
+ // 返回新的索引位置,以便 visit 继续遍历(跳过刚插入的节点)
64
+ // return (index + inserted_count)
65
+ return index + newNodes.length;
66
+ }
67
+ });
68
+ };
69
+ return transformer;
70
+ };
71
+
72
+ module.exports = remarkLoadingImage;
73
+ //# sourceMappingURL=imageLoadingPlugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageLoadingPlugin.cjs","sources":["../../src/utils/imageLoadingPlugin.tsx"],"sourcesContent":["import { visitParents } from 'unist-util-visit-parents';\nimport type { Plugin, Transformer } from 'unified';\nimport type { Node, Parent } from 'unist';\nimport type { Root, Text } from 'mdast';\n\n/**\n * 匹配未闭合的图片语法\n * 规则:\n * 1. (?<!\\\\) : 前面没有反斜杠(非转义)\n * 2. !\\[ : 以 ![ 开头\n * 3. [^\\]]* : 中括号内非 ] 字符\n * 4. \\] : 中括号闭合\n * 5. \\( : 接左圆括号\n * 6. [^)]*$ : 圆括号后直到字符串结束都不能有右括号\n */\nconst INCOMPLETE_IMAGE_REGEX = /(?<!\\\\)!\\[[^\\]]*\\]\\([^)]*$/;\n\n// 定义我们需要忽略的父级节点类型\nconst IGNORED_PARENTS = new Set(['code', 'inlineCode', 'link', 'image']);\n\n// 自定义 Loading 节点的接口定义(为了 TS 类型提示)\ninterface ImageLoadingNode extends Node {\n type: 'imageLoading';\n data: {\n hName: string;\n hProperties: {\n className: string[];\n [key: string]: unknown;\n };\n };\n}\n\nconst remarkLoadingImage: Plugin<[], Root> = () => {\n const transformer: Transformer<Root> = (tree) => {\n // 显式声明 node 为 Text 类型,ancestors 为 Node 数组\n visitParents(tree, 'text', (node: Text, ancestors: Node[]) => {\n const value = node.value;\n\n // === 1. 环境检测 (Context Awareness) ===\n // 检查祖先节点链中是否包含代码块、链接等不应该处理的类型\n const isInsideIgnoredElement = ancestors.some((parent) => \n IGNORED_PARENTS.has(parent.type)\n );\n\n if (isInsideIgnoredElement) {\n return;\n }\n\n // === 2. 正则匹配 ===\n const match = value.match(INCOMPLETE_IMAGE_REGEX);\n\n // 必须匹配,且匹配的内容必须位于字符串的**最末尾**\n if (!match || match.index === undefined || match.index + match[0].length !== value.length) {\n return;\n }\n\n // === 3. 节点替换逻辑 ===\n \n // 保留未完成图片语法之前的正常文本\n const cleanText = value.replace(INCOMPLETE_IMAGE_REGEX, '');\n const newNodes: Node[] = [];\n\n // 如果有正常文本,先推入一个 Text 节点\n if (cleanText) {\n newNodes.push({ type: 'text', value: cleanText } as Text);\n }\n\n // 构造自定义的 Loading 占位节点\n const loadingNode: ImageLoadingNode = {\n type: 'imageLoading',\n data: {\n // 指示 rehype 将其渲染为 div 标签\n hName: 'div', \n hProperties: {\n className: ['img-loading-placeholder'],\n 'data-content': '正在生成图片...',\n },\n },\n };\n newNodes.push(loadingNode);\n\n // === 4. 修改 AST 树 ===\n // 获取直接父节点 (Parent 类型)\n const parent = ancestors[ancestors.length - 1] as Parent;\n const index = parent.children.indexOf(node);\n\n if (index !== -1) {\n // 使用 splice 替换节点\n parent.children.splice(index, 1, ...newNodes);\n\n // 返回新的索引位置,以便 visit 继续遍历(跳过刚插入的节点)\n // return (index + inserted_count)\n return index + newNodes.length;\n }\n });\n };\n\n return transformer;\n};\n\nexport default remarkLoadingImage;"],"names":["visitParents"],"mappings":";;;;AAKA;;;;;;;;;AASG;AACH,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AAE5D;AACA,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAcnE,MAAA,kBAAkB,GAAqB,MAAK;AAChD,IAAA,MAAM,WAAW,GAAsB,CAAC,IAAI,KAAI;;QAE9CA,kBAAY,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,IAAU,EAAE,SAAiB,KAAI;AAC3D,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;;;YAIzB,MAAM,sBAAsB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,KACnD,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CACjC,CAAC;YAEF,IAAI,sBAAsB,EAAE;gBAC1B,OAAO;aACR;;YAGD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;;YAGlD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;gBACzF,OAAO;aACR;;;YAKD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;YAC5D,MAAM,QAAQ,GAAW,EAAE,CAAC;;YAG5B,IAAI,SAAS,EAAE;AACb,gBAAA,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAU,CAAC,CAAC;aAC3D;;AAGD,YAAA,MAAM,WAAW,GAAqB;AACpC,gBAAA,IAAI,EAAE,cAAc;AACpB,gBAAA,IAAI,EAAE;;AAEJ,oBAAA,KAAK,EAAE,KAAK;AACZ,oBAAA,WAAW,EAAE;wBACX,SAAS,EAAE,CAAC,yBAAyB,CAAC;AACtC,wBAAA,cAAc,EAAE,WAAW;AAC5B,qBAAA;AACF,iBAAA;aACF,CAAC;AACF,YAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;;;YAI3B,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5C,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;;AAEhB,gBAAA,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC;;;AAI9C,gBAAA,OAAO,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;aAChC;AACH,SAAC,CAAC,CAAC;AACL,KAAC,CAAC;AAEF,IAAA,OAAO,WAAW,CAAC;AACrB;;;;"}
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { Root } from 'mdast';
3
+ declare const remarkLoadingImage: Plugin<[], Root>;
4
+ export default remarkLoadingImage;
5
+ //# sourceMappingURL=imageLoadingPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageLoadingPlugin.d.ts","sourceRoot":"","sources":["../../src/utils/imageLoadingPlugin.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,SAAS,CAAC;AAEnD,OAAO,KAAK,EAAE,IAAI,EAAQ,MAAM,OAAO,CAAC;AA6BxC,QAAA,MAAM,kBAAkB,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,CAkExC,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -0,0 +1,71 @@
1
+ import { visitParents } from '../node_modules/.pnpm/unist-util-visit-parents@6.0.2/node_modules/unist-util-visit-parents/lib/index.js';
2
+
3
+ /**
4
+ * 匹配未闭合的图片语法
5
+ * 规则:
6
+ * 1. (?<!\\) : 前面没有反斜杠(非转义)
7
+ * 2. !\[ : 以 ![ 开头
8
+ * 3. [^\]]* : 中括号内非 ] 字符
9
+ * 4. \] : 中括号闭合
10
+ * 5. \( : 接左圆括号
11
+ * 6. [^)]*$ : 圆括号后直到字符串结束都不能有右括号
12
+ */
13
+ const INCOMPLETE_IMAGE_REGEX = /(?<!\\)!\[[^\]]*\]\([^)]*$/;
14
+ // 定义我们需要忽略的父级节点类型
15
+ const IGNORED_PARENTS = new Set(['code', 'inlineCode', 'link', 'image']);
16
+ const remarkLoadingImage = () => {
17
+ const transformer = (tree) => {
18
+ // 显式声明 node 为 Text 类型,ancestors 为 Node 数组
19
+ visitParents(tree, 'text', (node, ancestors) => {
20
+ const value = node.value;
21
+ // === 1. 环境检测 (Context Awareness) ===
22
+ // 检查祖先节点链中是否包含代码块、链接等不应该处理的类型
23
+ const isInsideIgnoredElement = ancestors.some((parent) => IGNORED_PARENTS.has(parent.type));
24
+ if (isInsideIgnoredElement) {
25
+ return;
26
+ }
27
+ // === 2. 正则匹配 ===
28
+ const match = value.match(INCOMPLETE_IMAGE_REGEX);
29
+ // 必须匹配,且匹配的内容必须位于字符串的**最末尾**
30
+ if (!match || match.index === undefined || match.index + match[0].length !== value.length) {
31
+ return;
32
+ }
33
+ // === 3. 节点替换逻辑 ===
34
+ // 保留未完成图片语法之前的正常文本
35
+ const cleanText = value.replace(INCOMPLETE_IMAGE_REGEX, '');
36
+ const newNodes = [];
37
+ // 如果有正常文本,先推入一个 Text 节点
38
+ if (cleanText) {
39
+ newNodes.push({ type: 'text', value: cleanText });
40
+ }
41
+ // 构造自定义的 Loading 占位节点
42
+ const loadingNode = {
43
+ type: 'imageLoading',
44
+ data: {
45
+ // 指示 rehype 将其渲染为 div 标签
46
+ hName: 'div',
47
+ hProperties: {
48
+ className: ['img-loading-placeholder'],
49
+ 'data-content': '正在生成图片...',
50
+ },
51
+ },
52
+ };
53
+ newNodes.push(loadingNode);
54
+ // === 4. 修改 AST 树 ===
55
+ // 获取直接父节点 (Parent 类型)
56
+ const parent = ancestors[ancestors.length - 1];
57
+ const index = parent.children.indexOf(node);
58
+ if (index !== -1) {
59
+ // 使用 splice 替换节点
60
+ parent.children.splice(index, 1, ...newNodes);
61
+ // 返回新的索引位置,以便 visit 继续遍历(跳过刚插入的节点)
62
+ // return (index + inserted_count)
63
+ return index + newNodes.length;
64
+ }
65
+ });
66
+ };
67
+ return transformer;
68
+ };
69
+
70
+ export { remarkLoadingImage as default };
71
+ //# sourceMappingURL=imageLoadingPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageLoadingPlugin.js","sources":["../../src/utils/imageLoadingPlugin.tsx"],"sourcesContent":["import { visitParents } from 'unist-util-visit-parents';\nimport type { Plugin, Transformer } from 'unified';\nimport type { Node, Parent } from 'unist';\nimport type { Root, Text } from 'mdast';\n\n/**\n * 匹配未闭合的图片语法\n * 规则:\n * 1. (?<!\\\\) : 前面没有反斜杠(非转义)\n * 2. !\\[ : 以 ![ 开头\n * 3. [^\\]]* : 中括号内非 ] 字符\n * 4. \\] : 中括号闭合\n * 5. \\( : 接左圆括号\n * 6. [^)]*$ : 圆括号后直到字符串结束都不能有右括号\n */\nconst INCOMPLETE_IMAGE_REGEX = /(?<!\\\\)!\\[[^\\]]*\\]\\([^)]*$/;\n\n// 定义我们需要忽略的父级节点类型\nconst IGNORED_PARENTS = new Set(['code', 'inlineCode', 'link', 'image']);\n\n// 自定义 Loading 节点的接口定义(为了 TS 类型提示)\ninterface ImageLoadingNode extends Node {\n type: 'imageLoading';\n data: {\n hName: string;\n hProperties: {\n className: string[];\n [key: string]: unknown;\n };\n };\n}\n\nconst remarkLoadingImage: Plugin<[], Root> = () => {\n const transformer: Transformer<Root> = (tree) => {\n // 显式声明 node 为 Text 类型,ancestors 为 Node 数组\n visitParents(tree, 'text', (node: Text, ancestors: Node[]) => {\n const value = node.value;\n\n // === 1. 环境检测 (Context Awareness) ===\n // 检查祖先节点链中是否包含代码块、链接等不应该处理的类型\n const isInsideIgnoredElement = ancestors.some((parent) => \n IGNORED_PARENTS.has(parent.type)\n );\n\n if (isInsideIgnoredElement) {\n return;\n }\n\n // === 2. 正则匹配 ===\n const match = value.match(INCOMPLETE_IMAGE_REGEX);\n\n // 必须匹配,且匹配的内容必须位于字符串的**最末尾**\n if (!match || match.index === undefined || match.index + match[0].length !== value.length) {\n return;\n }\n\n // === 3. 节点替换逻辑 ===\n \n // 保留未完成图片语法之前的正常文本\n const cleanText = value.replace(INCOMPLETE_IMAGE_REGEX, '');\n const newNodes: Node[] = [];\n\n // 如果有正常文本,先推入一个 Text 节点\n if (cleanText) {\n newNodes.push({ type: 'text', value: cleanText } as Text);\n }\n\n // 构造自定义的 Loading 占位节点\n const loadingNode: ImageLoadingNode = {\n type: 'imageLoading',\n data: {\n // 指示 rehype 将其渲染为 div 标签\n hName: 'div', \n hProperties: {\n className: ['img-loading-placeholder'],\n 'data-content': '正在生成图片...',\n },\n },\n };\n newNodes.push(loadingNode);\n\n // === 4. 修改 AST 树 ===\n // 获取直接父节点 (Parent 类型)\n const parent = ancestors[ancestors.length - 1] as Parent;\n const index = parent.children.indexOf(node);\n\n if (index !== -1) {\n // 使用 splice 替换节点\n parent.children.splice(index, 1, ...newNodes);\n\n // 返回新的索引位置,以便 visit 继续遍历(跳过刚插入的节点)\n // return (index + inserted_count)\n return index + newNodes.length;\n }\n });\n };\n\n return transformer;\n};\n\nexport default remarkLoadingImage;"],"names":[],"mappings":";;AAKA;;;;;;;;;AASG;AACH,MAAM,sBAAsB,GAAG,4BAA4B,CAAC;AAE5D;AACA,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAcnE,MAAA,kBAAkB,GAAqB,MAAK;AAChD,IAAA,MAAM,WAAW,GAAsB,CAAC,IAAI,KAAI;;QAE9C,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,IAAU,EAAE,SAAiB,KAAI;AAC3D,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;;;YAIzB,MAAM,sBAAsB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,KACnD,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CACjC,CAAC;YAEF,IAAI,sBAAsB,EAAE;gBAC1B,OAAO;aACR;;YAGD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;;YAGlD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE;gBACzF,OAAO;aACR;;;YAKD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,CAAC;YAC5D,MAAM,QAAQ,GAAW,EAAE,CAAC;;YAG5B,IAAI,SAAS,EAAE;AACb,gBAAA,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAU,CAAC,CAAC;aAC3D;;AAGD,YAAA,MAAM,WAAW,GAAqB;AACpC,gBAAA,IAAI,EAAE,cAAc;AACpB,gBAAA,IAAI,EAAE;;AAEJ,oBAAA,KAAK,EAAE,KAAK;AACZ,oBAAA,WAAW,EAAE;wBACX,SAAS,EAAE,CAAC,yBAAyB,CAAC;AACtC,wBAAA,cAAc,EAAE,WAAW;AAC5B,qBAAA;AACF,iBAAA;aACF,CAAC;AACF,YAAA,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;;;YAI3B,MAAM,MAAM,GAAG,SAAS,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAW,CAAC;YACzD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5C,YAAA,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;;AAEhB,gBAAA,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,QAAQ,CAAC,CAAC;;;AAI9C,gBAAA,OAAO,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;aAChC;AACH,SAAC,CAAC,CAAC;AACL,KAAC,CAAC;AAEF,IAAA,OAAO,WAAW,CAAC;AACrB;;;;"}
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ // 定义光标节点
4
+ const cursorNode = {
5
+ type: "element",
6
+ tagName: "span",
7
+ properties: {
8
+ className: ["aui-cursor-anchor"],
9
+ "aria-hidden": true,
10
+ },
11
+ children: [],
12
+ };
13
+ // 这些标签不能钻进去,只能插在后面
14
+ const VOID_TAGS = [
15
+ "img",
16
+ "br",
17
+ "hr",
18
+ "input",
19
+ "area",
20
+ "base",
21
+ "col",
22
+ "embed",
23
+ "meta",
24
+ "param",
25
+ "source",
26
+ "track",
27
+ "wbr",
28
+ ];
29
+ const rehypeAppendCursor = () => {
30
+ return (tree) => {
31
+ // 1. 从根节点开始
32
+ let parent = tree;
33
+ // 2. 简单的死循环向下钻,直到没有子元素或遇到文本
34
+ while (true) {
35
+ // 如果当前节点没有 children,或者 children 为空,停止
36
+ if (!parent.children || parent.children.length === 0) {
37
+ break;
38
+ }
39
+ // 获取最后一个子元素
40
+ const lastChild = parent.children[parent.children.length - 1];
41
+ // 核心判断:
42
+ // 只有当最后一个子元素是 'element' (标签) 时,我们才钻进去。
43
+ // 如果是 'text' (文本) 或者其他,说明我们已经到底了,直接停止。
44
+ if (lastChild.type === "element") {
45
+ // 如果是自闭合标签(如 <br>),也不能钻,停止
46
+ if (VOID_TAGS.includes(lastChild.tagName)) {
47
+ break;
48
+ }
49
+ // 继续向下钻
50
+ parent = lastChild;
51
+ }
52
+ else {
53
+ // 是文本节点,停止,光标就应该插在这个文本节点后面
54
+ break;
55
+ }
56
+ }
57
+ // 3. 安全插入
58
+ if (parent && Array.isArray(parent.children)) {
59
+ parent.children.push({ ...cursorNode });
60
+ }
61
+ };
62
+ };
63
+
64
+ exports.rehypeAppendCursor = rehypeAppendCursor;
65
+ //# sourceMappingURL=markdownCursorPlugin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownCursorPlugin.cjs","sources":["../../src/utils/markdownCursorPlugin.ts"],"sourcesContent":["// rehype-append-cursor.ts\nimport type { Plugin } from \"unified\";\nimport type { Root, Element, Content } from \"hast\";\n\n// 定义光标节点\nconst cursorNode: Element = {\n type: \"element\",\n tagName: \"span\",\n properties: {\n className: [\"aui-cursor-anchor\"],\n \"aria-hidden\": true,\n },\n children: [],\n};\n\n// 这些标签不能钻进去,只能插在后面\nconst VOID_TAGS = [\n \"img\",\n \"br\",\n \"hr\",\n \"input\",\n \"area\",\n \"base\",\n \"col\",\n \"embed\",\n \"meta\",\n \"param\",\n \"source\",\n \"track\",\n \"wbr\",\n];\n\nexport const rehypeAppendCursor: Plugin<[], Root> = () => {\n return (tree: Root) => {\n // 1. 从根节点开始\n let parent: Root | Element = tree;\n\n // 2. 简单的死循环向下钻,直到没有子元素或遇到文本\n while (true) {\n // 如果当前节点没有 children,或者 children 为空,停止\n if (!parent.children || parent.children.length === 0) {\n break;\n }\n\n // 获取最后一个子元素\n const lastChild = parent.children[parent.children.length - 1] as Element;\n\n // 核心判断:\n // 只有当最后一个子元素是 'element' (标签) 时,我们才钻进去。\n // 如果是 'text' (文本) 或者其他,说明我们已经到底了,直接停止。\n if (lastChild.type === \"element\") {\n // 如果是自闭合标签(如 <br>),也不能钻,停止\n if (VOID_TAGS.includes(lastChild.tagName)) {\n break;\n }\n // 继续向下钻\n parent = lastChild;\n } else {\n // 是文本节点,停止,光标就应该插在这个文本节点后面\n break;\n }\n }\n\n // 3. 安全插入\n if (parent && Array.isArray(parent.children)) {\n parent.children.push({ ...cursorNode });\n }\n };\n};\n"],"names":[],"mappings":";;AAIA;AACA,MAAM,UAAU,GAAY;AAC1B,IAAA,IAAI,EAAE,SAAS;AACf,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,UAAU,EAAE;QACV,SAAS,EAAE,CAAC,mBAAmB,CAAC;AAChC,QAAA,aAAa,EAAE,IAAI;AACpB,KAAA;AACD,IAAA,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF;AACA,MAAM,SAAS,GAAG;IAChB,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,MAAM;IACN,MAAM;IACN,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,KAAK;CACN,CAAC;AAEK,MAAM,kBAAkB,GAAqB,MAAK;IACvD,OAAO,CAAC,IAAU,KAAI;;QAEpB,IAAI,MAAM,GAAmB,IAAI,CAAC;;QAGlC,OAAO,IAAI,EAAE;;AAEX,YAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBACpD,MAAM;aACP;;AAGD,YAAA,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAY,CAAC;;;;AAKzE,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE;;gBAEhC,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;oBACzC,MAAM;iBACP;;gBAED,MAAM,GAAG,SAAS,CAAC;aACpB;iBAAM;;gBAEL,MAAM;aACP;SACF;;QAGD,IAAI,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;YAC5C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;SACzC;AACH,KAAC,CAAC;AACJ;;;;"}
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "unified";
2
+ import type { Root } from "hast";
3
+ export declare const rehypeAppendCursor: Plugin<[], Root>;
4
+ //# sourceMappingURL=markdownCursorPlugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownCursorPlugin.d.ts","sourceRoot":"","sources":["../../src/utils/markdownCursorPlugin.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,KAAK,EAAE,IAAI,EAAoB,MAAM,MAAM,CAAC;AA8BnD,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,CAoC/C,CAAC"}
@@ -0,0 +1,63 @@
1
+ // 定义光标节点
2
+ const cursorNode = {
3
+ type: "element",
4
+ tagName: "span",
5
+ properties: {
6
+ className: ["aui-cursor-anchor"],
7
+ "aria-hidden": true,
8
+ },
9
+ children: [],
10
+ };
11
+ // 这些标签不能钻进去,只能插在后面
12
+ const VOID_TAGS = [
13
+ "img",
14
+ "br",
15
+ "hr",
16
+ "input",
17
+ "area",
18
+ "base",
19
+ "col",
20
+ "embed",
21
+ "meta",
22
+ "param",
23
+ "source",
24
+ "track",
25
+ "wbr",
26
+ ];
27
+ const rehypeAppendCursor = () => {
28
+ return (tree) => {
29
+ // 1. 从根节点开始
30
+ let parent = tree;
31
+ // 2. 简单的死循环向下钻,直到没有子元素或遇到文本
32
+ while (true) {
33
+ // 如果当前节点没有 children,或者 children 为空,停止
34
+ if (!parent.children || parent.children.length === 0) {
35
+ break;
36
+ }
37
+ // 获取最后一个子元素
38
+ const lastChild = parent.children[parent.children.length - 1];
39
+ // 核心判断:
40
+ // 只有当最后一个子元素是 'element' (标签) 时,我们才钻进去。
41
+ // 如果是 'text' (文本) 或者其他,说明我们已经到底了,直接停止。
42
+ if (lastChild.type === "element") {
43
+ // 如果是自闭合标签(如 <br>),也不能钻,停止
44
+ if (VOID_TAGS.includes(lastChild.tagName)) {
45
+ break;
46
+ }
47
+ // 继续向下钻
48
+ parent = lastChild;
49
+ }
50
+ else {
51
+ // 是文本节点,停止,光标就应该插在这个文本节点后面
52
+ break;
53
+ }
54
+ }
55
+ // 3. 安全插入
56
+ if (parent && Array.isArray(parent.children)) {
57
+ parent.children.push({ ...cursorNode });
58
+ }
59
+ };
60
+ };
61
+
62
+ export { rehypeAppendCursor };
63
+ //# sourceMappingURL=markdownCursorPlugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdownCursorPlugin.js","sources":["../../src/utils/markdownCursorPlugin.ts"],"sourcesContent":["// rehype-append-cursor.ts\nimport type { Plugin } from \"unified\";\nimport type { Root, Element, Content } from \"hast\";\n\n// 定义光标节点\nconst cursorNode: Element = {\n type: \"element\",\n tagName: \"span\",\n properties: {\n className: [\"aui-cursor-anchor\"],\n \"aria-hidden\": true,\n },\n children: [],\n};\n\n// 这些标签不能钻进去,只能插在后面\nconst VOID_TAGS = [\n \"img\",\n \"br\",\n \"hr\",\n \"input\",\n \"area\",\n \"base\",\n \"col\",\n \"embed\",\n \"meta\",\n \"param\",\n \"source\",\n \"track\",\n \"wbr\",\n];\n\nexport const rehypeAppendCursor: Plugin<[], Root> = () => {\n return (tree: Root) => {\n // 1. 从根节点开始\n let parent: Root | Element = tree;\n\n // 2. 简单的死循环向下钻,直到没有子元素或遇到文本\n while (true) {\n // 如果当前节点没有 children,或者 children 为空,停止\n if (!parent.children || parent.children.length === 0) {\n break;\n }\n\n // 获取最后一个子元素\n const lastChild = parent.children[parent.children.length - 1] as Element;\n\n // 核心判断:\n // 只有当最后一个子元素是 'element' (标签) 时,我们才钻进去。\n // 如果是 'text' (文本) 或者其他,说明我们已经到底了,直接停止。\n if (lastChild.type === \"element\") {\n // 如果是自闭合标签(如 <br>),也不能钻,停止\n if (VOID_TAGS.includes(lastChild.tagName)) {\n break;\n }\n // 继续向下钻\n parent = lastChild;\n } else {\n // 是文本节点,停止,光标就应该插在这个文本节点后面\n break;\n }\n }\n\n // 3. 安全插入\n if (parent && Array.isArray(parent.children)) {\n parent.children.push({ ...cursorNode });\n }\n };\n};\n"],"names":[],"mappings":"AAIA;AACA,MAAM,UAAU,GAAY;AAC1B,IAAA,IAAI,EAAE,SAAS;AACf,IAAA,OAAO,EAAE,MAAM;AACf,IAAA,UAAU,EAAE;QACV,SAAS,EAAE,CAAC,mBAAmB,CAAC;AAChC,QAAA,aAAa,EAAE,IAAI;AACpB,KAAA;AACD,IAAA,QAAQ,EAAE,EAAE;CACb,CAAC;AAEF;AACA,MAAM,SAAS,GAAG;IAChB,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,MAAM;IACN,MAAM;IACN,KAAK;IACL,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,OAAO;IACP,KAAK;CACN,CAAC;AAEK,MAAM,kBAAkB,GAAqB,MAAK;IACvD,OAAO,CAAC,IAAU,KAAI;;QAEpB,IAAI,MAAM,GAAmB,IAAI,CAAC;;QAGlC,OAAO,IAAI,EAAE;;AAEX,YAAA,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBACpD,MAAM;aACP;;AAGD,YAAA,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAY,CAAC;;;;AAKzE,YAAA,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,EAAE;;gBAEhC,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;oBACzC,MAAM;iBACP;;gBAED,MAAM,GAAG,SAAS,CAAC;aACpB;iBAAM;;gBAEL,MAAM;aACP;SACF;;QAGD,IAAI,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;YAC5C,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC;SACzC;AACH,KAAC,CAAC;AACJ;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-lab/ui",
3
- "version": "0.1.0-beta.14",
3
+ "version": "0.1.0-beta.16",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -64,6 +64,7 @@
64
64
  "remark-math": "^6.0.0",
65
65
  "tailwind-merge": "^3.3.1",
66
66
  "tw-animate-css": "^1.4.0",
67
+ "unist-util-visit-parents": "^6.0.2",
67
68
  "use-sync-external-store": "^1.6.0",
68
69
  "uuid": "^13.0.0",
69
70
  "zustand": "^5.0.8"
@@ -75,11 +76,15 @@
75
76
  "@rollup/plugin-typescript": "^11.0.0",
76
77
  "@rollup/plugin-url": "^8.0.2",
77
78
  "@types/file-saver": "^2.0.7",
79
+ "@types/hast": "^3.0.4",
80
+ "@types/mdast": "^4.0.4",
78
81
  "@types/react": "17.0.2",
79
82
  "@types/react-dom": "17.0.2",
83
+ "@types/unist": "^3.0.3",
80
84
  "@vitejs/plugin-react": "^4.0.0",
81
85
  "autoprefixer": "^10.4.21",
82
86
  "less": "^4.4.2",
87
+ "mdast": "^3.0.0",
83
88
  "postcss": "^8.5.6",
84
89
  "postcss-prefix-selector": "^2.1.1",
85
90
  "rollup": "^3.0.0",
@@ -89,6 +94,8 @@
89
94
  "sass": "^1.93.3",
90
95
  "tailwindcss": "3",
91
96
  "typescript": "^5.0.2",
97
+ "unified": "^11.0.5",
98
+ "unist": "^0.0.1",
92
99
  "vite": "^4.4.5",
93
100
  "@chatkit/core": "^1.0.0"
94
101
  },