@will1123/lx-ui-utils 1.0.6 → 1.0.8

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/es/helper.js CHANGED
@@ -18,6 +18,45 @@ var __async = (__this, __arguments, generator) => {
18
18
  step((generator = generator.apply(__this, __arguments)).next());
19
19
  });
20
20
  };
21
+ import { v4 } from "uuid";
22
+ import { saveAs as saveAs$1 } from "file-saver";
23
+ import * as XLSX from "xlsx";
24
+ function generateUniqueId() {
25
+ return v4();
26
+ }
27
+ function saveAs(data, filename) {
28
+ const defaultName = filename || (data instanceof File ? data.name : "download");
29
+ saveAs$1(data, defaultName);
30
+ }
31
+ function generateExcel(data, options) {
32
+ const {
33
+ sheetName = "Sheet1",
34
+ headers
35
+ } = options || {};
36
+ const worksheet = XLSX.utils.json_to_sheet(data, {
37
+ header: headers ? Object.keys(headers) : void 0
38
+ });
39
+ if (headers && worksheet["!ref"]) {
40
+ const range = XLSX.utils.decode_range(worksheet["!ref"]);
41
+ const headerRow = range.s.r;
42
+ Object.keys(headers).forEach((key, index) => {
43
+ const cellAddress = XLSX.utils.encode_cell({
44
+ r: headerRow,
45
+ c: index
46
+ });
47
+ worksheet[cellAddress].v = headers[key];
48
+ });
49
+ }
50
+ const workbook = XLSX.utils.book_new();
51
+ XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
52
+ const excelBuffer = XLSX.write(workbook, {
53
+ bookType: "xlsx",
54
+ type: "array"
55
+ });
56
+ return new Blob([excelBuffer], {
57
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
58
+ });
59
+ }
21
60
  function copyToClipboard(text) {
22
61
  return __async(this, null, function* () {
23
62
  if (typeof window === "undefined") return false;
@@ -54,6 +93,9 @@ function isDom(val) {
54
93
  }
55
94
  export {
56
95
  copyToClipboard,
57
- isDom
96
+ generateExcel,
97
+ generateUniqueId,
98
+ isDom,
99
+ saveAs
58
100
  };
59
101
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sources":["../../src/helper.ts"],"sourcesContent":["/**\r\n * 复制文本到剪贴板\r\n *\r\n * @param text - 要复制的文本\r\n * @returns Promise<boolean> - 成功返回 true,失败返回 false\r\n *\r\n * @example\r\n * ```typescript\r\n * import { copyToClipboard } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const success = await copyToClipboard('Hello World')\r\n * if (success) {\r\n * console.log('复制成功')\r\n * } else {\r\n * console.log('复制失败')\r\n * }\r\n *\r\n * // 复制代码\r\n * const code = 'console.log(\"Hello World\")'\r\n * await copyToClipboard(code)\r\n *\r\n * // 复制 HTML 内容\r\n * const html = '<div>Hello</div>'\r\n * await copyToClipboard(html)\r\n * ```\r\n */\r\nexport async function copyToClipboard(text: string): Promise<boolean> {\r\n if (typeof window === 'undefined') return false;\r\n\r\n // 使用现代 Clipboard API\r\n if (navigator.clipboard && navigator.clipboard.writeText) {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n return false;\r\n }\r\n }\r\n\r\n // 降级方案:使用 document.execCommand\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n textArea.style.position = 'fixed';\r\n textArea.style.opacity = '0';\r\n textArea.style.left = '-9999px';\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n try {\r\n const successful = document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n return successful;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n document.body.removeChild(textArea);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 判断值是否为 DOM 元素(HTMLElement 或 Document)\r\n *\r\n * @param val - 要判断的值\r\n * @returns 是否为 DOM 元素\r\n *\r\n * @example\r\n * ```typescript\r\n * import { isDom } from '@will1123/lx-ui-utils'\r\n *\r\n * isDom(document.body) // true\r\n * isDom(document) // true\r\n * isDom({}) // false\r\n * isDom('div') // false\r\n * ```\r\n */\r\nexport function isDom(val: unknown): val is HTMLElement | Document {\r\n return val instanceof HTMLElement || val instanceof Document;\r\n}\r\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,SAAsB,gBAAgB,MAAgC;AAAA;AACpE,QAAI,OAAO,WAAW,YAAa,QAAO;AAG1C,QAAI,UAAU,aAAa,UAAU,UAAU,WAAW;AACxD,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,IAAI;AACxC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,SAAS,KAAK;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,SAAS,cAAc,UAAU;AAClD,aAAS,QAAQ;AACjB,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,UAAU;AACzB,aAAS,MAAM,OAAO;AACtB,aAAS,KAAK,YAAY,QAAQ;AAClC,aAAS,MAAA;AACT,aAAS,OAAA;AAET,QAAI;AACF,YAAM,aAAa,SAAS,YAAY,MAAM;AAC9C,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,SAAS,KAAK;AAC5B,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAkBO,SAAS,MAAM,KAA6C;AACjE,SAAO,eAAe,eAAe,eAAe;AACtD;"}
1
+ {"version":3,"file":"helper.js","sources":["../../src/helper.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\r\nimport { saveAs as fileSaveAs } from 'file-saver';\r\nimport * as XLSX from 'xlsx';\r\n\r\n/**\r\n * 生成唯一 ID\r\n *\r\n * 基于 UUID v4 生成唯一标识符\r\n *\r\n * @returns 唯一 ID 字符串(格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)\r\n *\r\n * @example\r\n * ```typescript\r\n * import { generateUniqueId } from '@will1123/lx-ui-utils'\r\n *\r\n * // 生成唯一 ID\r\n * const id = generateUniqueId()\r\n * console.log(id) // 输出:'550e8400-e29b-41d4-a716-446655440000'\r\n *\r\n * // 用于生成唯一 key\r\n * const items = [1, 2, 3].map(item => ({\r\n * id: generateUniqueId(),\r\n * data: item\r\n * }))\r\n *\r\n * // 用于生成临时标识\r\n * const tempId = generateUniqueId()\r\n * ```\r\n */\r\nexport function generateUniqueId(): string {\r\n return uuidv4();\r\n}\r\n\r\n/**\r\n * 保存文件到本地\r\n *\r\n * 基于 file-saver 实现文件保存功能,支持多种数据类型\r\n *\r\n * @param data - 要保存的数据,支持 Blob、File\r\n * @param filename - 文件名\r\n *\r\n * @example\r\n * ```typescript\r\n * import { saveAs } from '@will1123/lx-ui-utils'\r\n *\r\n * // 保存 Blob\r\n * const blob = new Blob(['Hello'], { type: 'text/plain;charset=utf-8' })\r\n * saveAs(blob, 'greeting.txt')\r\n *\r\n * // 保存文件(File 对象)\r\n * saveAs(file, 'backup.txt')\r\n *\r\n * // 不指定文件名(使用默认)\r\n * saveAs(blob)\r\n * ```\r\n */\r\nexport function saveAs(data: Blob | File, filename?: string): void {\r\n const defaultName =\r\n filename || (data instanceof File ? data.name : 'download');\r\n\r\n fileSaveAs(data, defaultName);\r\n}\r\n\r\n/**\r\n * 生成 Excel 文件\r\n *\r\n * 基于 xlsx 库实现 Excel 文件生成功能,支持自定义列名和工作表名称\r\n *\r\n * @param data - 要转换的数据数组(对象数组)\r\n * @param options - 生成选项\r\n * @param options.sheetName - 工作表名称(默认 'Sheet1')\r\n * @param options.headers - 自定义列名(key: 字段名, value: 列标题)\r\n * @returns Excel 文件的 Blob 对象\r\n *\r\n * @example\r\n * ```typescript\r\n * import { generateExcel, saveAs } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const data = [\r\n * { name: '张三', age: 25, city: '北京' },\r\n * { name: '李四', age: 30, city: '上海' }\r\n * ]\r\n * const blob = generateExcel(data)\r\n * saveAs(blob, '用户数据.xlsx')\r\n *\r\n * // 指定工作表名称\r\n * const blob = generateExcel(data, {\r\n * sheetName: '用户列表'\r\n * })\r\n *\r\n * // 自定义列名\r\n * const blob = generateExcel(data, {\r\n * headers: {\r\n * name: '姓名',\r\n * age: '年龄',\r\n * city: '城市'\r\n * }\r\n * })\r\n * saveAs(blob, '用户数据.xlsx')\r\n * ```\r\n */\r\nexport function generateExcel(\r\n data: Record<string, any>[],\r\n options?: {\r\n sheetName?: string;\r\n headers?: Record<string, string>;\r\n }\r\n): Blob {\r\n const {\r\n sheetName = 'Sheet1',\r\n headers\r\n } = options || {};\r\n\r\n // 创建工作表\r\n const worksheet = XLSX.utils.json_to_sheet(data, {\r\n header: headers ? Object.keys(headers) : undefined\r\n });\r\n\r\n // 自定义表头\r\n if (headers && worksheet['!ref']) {\r\n const range = XLSX.utils.decode_range(worksheet['!ref']);\r\n const headerRow = range.s.r;\r\n\r\n Object.keys(headers).forEach((key, index) => {\r\n const cellAddress = XLSX.utils.encode_cell({\r\n r: headerRow,\r\n c: index\r\n });\r\n worksheet[cellAddress].v = headers[key];\r\n });\r\n }\r\n\r\n // 创建工作簿\r\n const workbook = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);\r\n\r\n // 写入文件并返回 Blob\r\n const excelBuffer = XLSX.write(workbook, {\r\n bookType: 'xlsx',\r\n type: 'array'\r\n });\r\n\r\n return new Blob([excelBuffer], {\r\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\r\n });\r\n}\r\n\r\n/**\r\n * 复制文本到剪贴板\r\n *\r\n * @param text - 要复制的文本\r\n * @returns Promise<boolean> - 成功返回 true,失败返回 false\r\n *\r\n * @example\r\n * ```typescript\r\n * import { copyToClipboard } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const success = await copyToClipboard('Hello World')\r\n * if (success) {\r\n * console.log('复制成功')\r\n * } else {\r\n * console.log('复制失败')\r\n * }\r\n *\r\n * // 复制代码\r\n * const code = 'console.log(\"Hello World\")'\r\n * await copyToClipboard(code)\r\n *\r\n * // 复制 HTML 内容\r\n * const html = '<div>Hello</div>'\r\n * await copyToClipboard(html)\r\n * ```\r\n */\r\nexport async function copyToClipboard(text: string): Promise<boolean> {\r\n if (typeof window === 'undefined') return false;\r\n\r\n // 使用现代 Clipboard API\r\n if (navigator.clipboard && navigator.clipboard.writeText) {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n return false;\r\n }\r\n }\r\n\r\n // 降级方案:使用 document.execCommand\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n textArea.style.position = 'fixed';\r\n textArea.style.opacity = '0';\r\n textArea.style.left = '-9999px';\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n try {\r\n const successful = document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n return successful;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n document.body.removeChild(textArea);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 判断值是否为 DOM 元素(HTMLElement 或 Document)\r\n *\r\n * @param val - 要判断的值\r\n * @returns 是否为 DOM 元素\r\n *\r\n * @example\r\n * ```typescript\r\n * import { isDom } from '@will1123/lx-ui-utils'\r\n *\r\n * isDom(document.body) // true\r\n * isDom(document) // true\r\n * isDom({}) // false\r\n * isDom('div') // false\r\n * ```\r\n */\r\nexport function isDom(val: unknown): val is HTMLElement | Document {\r\n return val instanceof HTMLElement || val instanceof Document;\r\n}\r\n"],"names":["uuidv4","fileSaveAs"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA6BO,SAAS,mBAA2B;AACzC,SAAOA,GAAA;AACT;AAyBO,SAAS,OAAO,MAAmB,UAAyB;AACjE,QAAM,cACJ,aAAa,gBAAgB,OAAO,KAAK,OAAO;AAElDC,WAAW,MAAM,WAAW;AAC9B;AAyCO,SAAS,cACd,MACA,SAIM;AACN,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,EAAA,IACE,WAAW,CAAA;AAGf,QAAM,YAAY,KAAK,MAAM,cAAc,MAAM;AAAA,IAC/C,QAAQ,UAAU,OAAO,KAAK,OAAO,IAAI;AAAA,EAAA,CAC1C;AAGD,MAAI,WAAW,UAAU,MAAM,GAAG;AAChC,UAAM,QAAQ,KAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AACvD,UAAM,YAAY,MAAM,EAAE;AAE1B,WAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,KAAK,UAAU;AAC3C,YAAM,cAAc,KAAK,MAAM,YAAY;AAAA,QACzC,GAAG;AAAA,QACH,GAAG;AAAA,MAAA,CACJ;AACD,gBAAU,WAAW,EAAE,IAAI,QAAQ,GAAG;AAAA,IACxC,CAAC;AAAA,EACH;AAGA,QAAM,WAAW,KAAK,MAAM,SAAA;AAC5B,OAAK,MAAM,kBAAkB,UAAU,WAAW,SAAS;AAG3D,QAAM,cAAc,KAAK,MAAM,UAAU;AAAA,IACvC,UAAU;AAAA,IACV,MAAM;AAAA,EAAA,CACP;AAED,SAAO,IAAI,KAAK,CAAC,WAAW,GAAG;AAAA,IAC7B,MAAM;AAAA,EAAA,CACP;AACH;AA6BA,SAAsB,gBAAgB,MAAgC;AAAA;AACpE,QAAI,OAAO,WAAW,YAAa,QAAO;AAG1C,QAAI,UAAU,aAAa,UAAU,UAAU,WAAW;AACxD,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,IAAI;AACxC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,SAAS,KAAK;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,SAAS,cAAc,UAAU;AAClD,aAAS,QAAQ;AACjB,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,UAAU;AACzB,aAAS,MAAM,OAAO;AACtB,aAAS,KAAK,YAAY,QAAQ;AAClC,aAAS,MAAA;AACT,aAAS,OAAA;AAET,QAAI;AACF,YAAM,aAAa,SAAS,YAAY,MAAM;AAC9C,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,SAAS,KAAK;AAC5B,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAkBO,SAAS,MAAM,KAA6C;AACjE,SAAO,eAAe,eAAe,eAAe;AACtD;"}
package/dist/es/index.js CHANGED
@@ -1,14 +1,19 @@
1
1
  import { sse } from "./sse.js";
2
2
  import { extractThinking } from "./thinking.js";
3
- import { bindMarkdownCodeBoxEvents, createMarkdownRender, getMarkdownCodeTheme, setMarkdownCodeTheme, toggleMarkdownCodeTheme, updateMarkdownCodeBlocksTheme } from "./markdown.js";
4
- import { copyToClipboard, isDom } from "./helper.js";
3
+ import { bindMarkdownCodeBoxEvents, createMarkdownParser, getMarkdownCodeTheme, setMarkdownCodeTheme, toggleMarkdownCodeTheme, updateMarkdownCodeBlocksTheme } from "./markdown.js";
4
+ import { copyToClipboard, generateExcel, generateUniqueId, isDom, saveAs } from "./helper.js";
5
+ import { getBaseMessageOption } from "./message-config.js";
5
6
  export {
6
7
  bindMarkdownCodeBoxEvents,
7
8
  copyToClipboard,
8
- createMarkdownRender,
9
+ createMarkdownParser,
9
10
  extractThinking,
11
+ generateExcel,
12
+ generateUniqueId,
13
+ getBaseMessageOption,
10
14
  getMarkdownCodeTheme,
11
15
  isDom,
16
+ saveAs,
12
17
  setMarkdownCodeTheme,
13
18
  sse,
14
19
  toggleMarkdownCodeTheme,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
@@ -63,12 +63,12 @@ function updateMarkdownCodeBlocksTheme(themeOrContainer, theme) {
63
63
  container = themeOrContainer;
64
64
  actualTheme = theme;
65
65
  }
66
- const codeBoxes = container.querySelectorAll(".lx-ui-code-box");
66
+ const codeBoxes = container.querySelectorAll(".lx-chat-markdown__code-box");
67
67
  codeBoxes.forEach((box) => {
68
68
  box.setAttribute("data-theme", actualTheme);
69
69
  });
70
70
  }
71
- function createMarkdownRender(createConfig = {}) {
71
+ function createMarkdownParser(createConfig = {}) {
72
72
  const {
73
73
  codeBoxToolEnable = true,
74
74
  katexEnable = true,
@@ -83,37 +83,37 @@ function createMarkdownRender(createConfig = {}) {
83
83
  const originalTableRenderer = markedRenderer.table;
84
84
  markedRenderer.table = (...arg) => {
85
85
  const html = originalTableRenderer.call(markedRenderer, ...arg);
86
- return `<div class="lx-ui-table-wrapper"><div class="lx-ui-table-box">${html}</div></div>`;
86
+ return `<div class="lx-chat-markdown__table-wrapper"><div class="lx-chat-markdown__table-box">${html}</div></div>`;
87
87
  };
88
88
  const originalCodeRenderer = markedRenderer.code;
89
89
  markedRenderer.code = (token) => {
90
90
  const html = originalCodeRenderer.call(markedRenderer, token);
91
91
  const theme = getMarkdownCodeTheme();
92
92
  const lang = (token == null ? void 0 : token.lang) || "";
93
- const codeBoxToolEnableHtml = `<div class="lx-ui-code-box-header">
94
- <div class="lx-ui-code-box-header-left">
95
- <span class="lx-ui-code-language">${lang}</span>
93
+ const codeBoxToolEnableHtml = `<div class="lx-chat-markdown__code-box-header">
94
+ <div class="lx-chat-markdown__code-box-header-left">
95
+ <span class="lx-chat-markdown__code-language">${lang}</span>
96
96
  </div>
97
- <div class="lx-ui-code-box-header-right">
98
- <i class="lx-ui-code-box-header-copy lx-ui-icon-copy" title="复制">复制</i>
99
- <i class="lx-ui-code-box-header-theme lx-ui-icon-theme" title="切换主题">切换主题</i>
97
+ <div class="lx-chat-markdown__code-box-header-right">
98
+ <i class="lx-icon-fuzhi lx-chat-markdown__icon-copy" title="复制"></i>
99
+ <i class="lx-icon-taiyang lx-chat-markdown__icon-theme" title="切换主题"></i>
100
100
  </div>
101
101
  </div>`;
102
- const codeBoxToolDisableHtml = `<div class="lx-ui-code-box-header">
103
- <div class="lx-ui-code-box-header-left">
104
- <span class="lx-ui-code-language">${lang}</span>
102
+ const codeBoxToolDisableHtml = `<div class="lx-chat-markdown__code-box-header">
103
+ <div class="lx-chat-markdown__code-box-header-left">
104
+ <span class="lx-chat-markdown__code-language">${lang}</span>
105
105
  </div>
106
106
  </div>`;
107
- return `<div class="lx-ui-code-box-wrapper">
108
- <div class="lx-ui-code-box" data-theme="${theme}">
107
+ return `<div class="lx-chat-markdown__code-box-wrapper">
108
+ <div class="lx-chat-markdown__code-box" data-theme="${theme}">
109
109
  ${codeBoxToolEnable ? codeBoxToolEnableHtml : codeBoxToolDisableHtml}
110
- <div class="lx-ui-code-box-content">
110
+ <div class="lx-chat-markdown__code-box-content">
111
111
  ${html}
112
112
  </div>
113
113
  </div>
114
114
  </div>`;
115
115
  };
116
- const markdownRender = new Marked(
116
+ const markdownParser = new Marked(
117
117
  markedHighlight({
118
118
  langPrefix: "hljs language-",
119
119
  highlight(code, lang) {
@@ -122,13 +122,13 @@ function createMarkdownRender(createConfig = {}) {
122
122
  }
123
123
  })
124
124
  );
125
- markdownRender.setOptions({
125
+ markdownParser.setOptions({
126
126
  renderer: markedRenderer
127
127
  });
128
128
  if (katexEnable) {
129
- markdownRender.use(createKatexExtension(katexOptions));
129
+ markdownParser.use(createKatexExtension(katexOptions));
130
130
  }
131
- return markdownRender;
131
+ return markdownParser;
132
132
  }
133
133
  function createKatexExtension(katexOptions) {
134
134
  return {
@@ -151,7 +151,7 @@ function createKatexExtension(katexOptions) {
151
151
  return {
152
152
  type: "blockMath",
153
153
  raw: match[0],
154
- html: `<div class="lx-ui-katex-block">${html}</div>`
154
+ html: `<div class="lx-chat-markdown__katex-block">${html}</div>`
155
155
  };
156
156
  } catch (e) {
157
157
  console.error("块级公式渲染错误:", e);
@@ -185,7 +185,7 @@ function createKatexExtension(katexOptions) {
185
185
  return {
186
186
  type: "inlineMath",
187
187
  raw: match[0],
188
- html: `<span class="lx-ui-katex-inline">${html}</span>`
188
+ html: `<span class="lx-chat-markdown__katex-inline">${html}</span>`
189
189
  };
190
190
  } catch (e) {
191
191
  console.error("行内公式渲染错误:", e);
@@ -224,10 +224,10 @@ function bindMarkdownCodeBoxEvents(containerOrConfig, config) {
224
224
  const { onCopySuccess, onCopyError, onThemeChange } = eventsConfig;
225
225
  const clickHandler = (e) => __async(this, null, function* () {
226
226
  const target = e.target;
227
- const copyButton = target.closest(".lx-ui-icon-copy");
227
+ const copyButton = target.closest(".lx-chat-markdown__icon-copy");
228
228
  if (copyButton) {
229
- const codeBox = copyButton.closest(".lx-ui-code-box");
230
- const codeElement = codeBox == null ? void 0 : codeBox.querySelector(".lx-ui-code-box-content");
229
+ const codeBox = copyButton.closest(".lx-chat-markdown__code-box");
230
+ const codeElement = codeBox == null ? void 0 : codeBox.querySelector(".lx-chat-markdown__code-box-content");
231
231
  if (codeElement) {
232
232
  const text = (codeElement.textContent || "").trim();
233
233
  const success = yield copyToClipboard(text);
@@ -241,7 +241,7 @@ function bindMarkdownCodeBoxEvents(containerOrConfig, config) {
241
241
  e.preventDefault();
242
242
  return;
243
243
  }
244
- const themeButton = target.closest(".lx-ui-icon-theme");
244
+ const themeButton = target.closest(".lx-chat-markdown__icon-theme");
245
245
  if (themeButton) {
246
246
  const newTheme = toggleMarkdownCodeTheme();
247
247
  updateMarkdownCodeBlocksTheme(newTheme);
@@ -258,7 +258,7 @@ function bindMarkdownCodeBoxEvents(containerOrConfig, config) {
258
258
  }
259
259
  export {
260
260
  bindMarkdownCodeBoxEvents,
261
- createMarkdownRender,
261
+ createMarkdownParser,
262
262
  getMarkdownCodeTheme,
263
263
  setMarkdownCodeTheme,
264
264
  toggleMarkdownCodeTheme,
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sources":["../../src/markdown.ts"],"sourcesContent":["import { marked, Marked } from 'marked';\r\nimport { markedHighlight } from 'marked-highlight';\r\nimport hljs from 'highlight.js';\r\nimport katex from 'katex';\r\nimport { copyToClipboard, isDom } from './helper';\r\n\r\n/**\r\n * 代码主题类型\r\n */\r\nexport type CodeTheme = 'dark' | 'light';\r\n\r\n/**\r\n * createMarkdownRender 函数配置\r\n */\r\nexport interface CreateMarkdownRenderConfig {\r\n /** 代码块工具栏是否启用(默认 true) */\r\n codeBoxToolEnable?: boolean;\r\n /** 是否启用 KaTeX 公式支持(默认 true) */\r\n katexEnable?: boolean;\r\n /** KaTeX 配置选项 */\r\n katexOptions?: katex.KatexOptions;\r\n}\r\n\r\n/**\r\n * 代码块工具栏事件配置\r\n */\r\nexport interface CodeBoxEventsConfig {\r\n /** 复制成功回调 */\r\n onCopySuccess?: (text: string) => void;\r\n /** 复制失败回调 */\r\n onCopyError?: (error: Error) => void;\r\n /** 主题切换回调 */\r\n onThemeChange?: (theme: CodeTheme) => void;\r\n}\r\n\r\n/**\r\n * 从 localStorage 获取代码主题\r\n */\r\nexport function getMarkdownCodeTheme(): CodeTheme {\r\n if (typeof window === 'undefined') return 'dark';\r\n return (localStorage.getItem('LxUIMdCodeTheme') as CodeTheme) || 'dark';\r\n}\r\n\r\n/**\r\n * 设置代码主题到 localStorage\r\n */\r\nexport function setMarkdownCodeTheme(theme: CodeTheme): void {\r\n if (typeof window === 'undefined') return;\r\n localStorage.setItem('LxUIMdCodeTheme', theme);\r\n}\r\n\r\n/**\r\n * 切换代码主题\r\n *\r\n * @returns 新主题\r\n *\r\n * @example\r\n * ```typescript\r\n * import { toggleMarkdownCodeTheme } from '@will1123/lx-ui-utils'\r\n *\r\n * // 切换主题\r\n * const newTheme = toggleMarkdownCodeTheme()\r\n * // 注意:此函数只切换 localStorage 中的主题,不会更新 DOM\r\n * // 如需更新 DOM,请配合 updateMarkdownCodeBlocksTheme\r\n * ```\r\n */\r\nexport function toggleMarkdownCodeTheme(): CodeTheme {\r\n const currentTheme = getMarkdownCodeTheme();\r\n const newTheme = currentTheme === 'dark' ? 'light' : 'dark';\r\n setMarkdownCodeTheme(newTheme);\r\n return newTheme;\r\n}\r\n\r\n/**\r\n * 更新 DOM 中所有代码块的主题\r\n *\r\n * @param themeOrContainer - 主题或容器元素(字符串表示主题,DOM 元素表示容器)\r\n * @param theme - 主题(当第一个参数是容器时使用)\r\n *\r\n * @example\r\n * ```typescript\r\n * import { updateMarkdownCodeBlocksTheme } from '@will1123/lx-ui-utils'\r\n *\r\n * // 方式 1:只传主题(更新 document.body 中的所有代码块)\r\n * updateMarkdownCodeBlocksTheme('dark')\r\n *\r\n * // 方式 2:传容器和主题\r\n * const container = document.querySelector('#markdown-container')\r\n * updateMarkdownCodeBlocksTheme(container, 'light')\r\n * ```\r\n */\r\nexport function updateMarkdownCodeBlocksTheme(\r\n themeOrContainer: CodeTheme | HTMLElement | Document,\r\n theme?: CodeTheme\r\n): void {\r\n if (typeof window === 'undefined') return;\r\n\r\n // 解析参数\r\n let actualTheme: CodeTheme;\r\n let container: HTMLElement | Document = document.body;\r\n\r\n if (typeof themeOrContainer === 'string') {\r\n // 第一个参数是 theme\r\n actualTheme = themeOrContainer;\r\n } else {\r\n // 第一个参数是 container\r\n container = themeOrContainer;\r\n actualTheme = theme as CodeTheme;\r\n }\r\n\r\n const codeBoxes = container.querySelectorAll('.lx-ui-code-box');\r\n codeBoxes.forEach(box => {\r\n (box as HTMLElement).setAttribute('data-theme', actualTheme);\r\n });\r\n}\r\n\r\n/**\r\n * 创建配置好的 Marked 实例\r\n */\r\nexport function createMarkdownRender(\r\n createConfig: CreateMarkdownRenderConfig = {}\r\n) {\r\n const {\r\n codeBoxToolEnable = true,\r\n katexEnable = true,\r\n katexOptions\r\n } = createConfig;\r\n\r\n const markedRenderer = new marked.Renderer();\r\n\r\n // 重写链接渲染器 - 自动添加 target=\"_blank\"\r\n const originalLinkRenderer = markedRenderer.link;\r\n markedRenderer.link = (...arg) => {\r\n const html = originalLinkRenderer.call(markedRenderer, ...arg);\r\n return html.replace(/^<a /, '<a target=\"_blank\" ');\r\n };\r\n\r\n // 重写表格渲染器 - 包装在可滚动容器中\r\n const originalTableRenderer = markedRenderer.table;\r\n markedRenderer.table = (...arg) => {\r\n const html = originalTableRenderer.call(markedRenderer, ...arg);\r\n return `<div class=\"lx-ui-table-wrapper\"><div class=\"lx-ui-table-box\">${html}</div></div>`;\r\n };\r\n\r\n // 重写代码块渲染器 - 添加自定义样式和主题\r\n const originalCodeRenderer = markedRenderer.code;\r\n markedRenderer.code = token => {\r\n const html = originalCodeRenderer.call(markedRenderer, token);\r\n const theme = getMarkdownCodeTheme();\r\n const lang = token?.lang || '';\r\n\r\n // 代码块工具栏 HTML(启用工具栏)\r\n const codeBoxToolEnableHtml = `<div class=\"lx-ui-code-box-header\">\r\n <div class=\"lx-ui-code-box-header-left\">\r\n <span class=\"lx-ui-code-language\">${lang}</span>\r\n </div>\r\n <div class=\"lx-ui-code-box-header-right\">\r\n <i class=\"lx-ui-code-box-header-copy lx-ui-icon-copy\" title=\"复制\">复制</i>\r\n <i class=\"lx-ui-code-box-header-theme lx-ui-icon-theme\" title=\"切换主题\">切换主题</i>\r\n </div>\r\n </div>`;\r\n\r\n // 代码块工具栏 HTML(禁用工具栏)\r\n const codeBoxToolDisableHtml = `<div class=\"lx-ui-code-box-header\">\r\n <div class=\"lx-ui-code-box-header-left\">\r\n <span class=\"lx-ui-code-language\">${lang}</span>\r\n </div>\r\n </div>`;\r\n\r\n return `<div class=\"lx-ui-code-box-wrapper\">\r\n <div class=\"lx-ui-code-box\" data-theme=\"${theme}\">\r\n ${codeBoxToolEnable ? codeBoxToolEnableHtml : codeBoxToolDisableHtml}\r\n <div class=\"lx-ui-code-box-content\">\r\n ${html}\r\n </div>\r\n </div>\r\n </div>`;\r\n };\r\n\r\n // 创建 Marked 实例并配置语法高亮\r\n const markdownRender = new Marked(\r\n markedHighlight({\r\n langPrefix: 'hljs language-',\r\n highlight(code, lang) {\r\n const language = hljs.getLanguage(lang) ? lang : 'plaintext';\r\n return hljs.highlight(code, { language }).value;\r\n }\r\n })\r\n );\r\n\r\n // 设置渲染器选项\r\n markdownRender.setOptions({\r\n renderer: markedRenderer\r\n });\r\n\r\n // 如果启用了 KaTeX,注册 KaTeX 扩展\r\n if (katexEnable) {\r\n markdownRender.use(createKatexExtension(katexOptions));\r\n }\r\n\r\n return markdownRender;\r\n}\r\n\r\n/**\r\n * 创建 KaTeX Marked 扩展\r\n * @param katexOptions - KaTeX 配置选项\r\n * @returns Marked 扩展对象\r\n */\r\nfunction createKatexExtension(katexOptions?: katex.KatexOptions) {\r\n return {\r\n extensions: [\r\n {\r\n name: 'blockMath',\r\n level: 'block' as const,\r\n start(src: string) {\r\n return src.indexOf('$$');\r\n },\r\n tokenizer(src: string) {\r\n const match = src.match(/^\\$\\$([\\s\\S]*?)\\$\\$(?:\\n|$)/);\r\n if (match) {\r\n try {\r\n const formula = match[1].trim();\r\n const html = katex.renderToString(formula, {\r\n displayMode: true,\r\n throwOnError: false,\r\n ...katexOptions\r\n });\r\n return {\r\n type: 'blockMath',\r\n raw: match[0],\r\n html: `<div class=\"lx-ui-katex-block\">${html}</div>`\r\n };\r\n } catch (e) {\r\n console.error('块级公式渲染错误:', e);\r\n return {\r\n type: 'blockMath',\r\n raw: match[0],\r\n html: match[0]\r\n };\r\n }\r\n }\r\n },\r\n renderer(token: any) {\r\n return token.html || '';\r\n }\r\n },\r\n {\r\n name: 'inlineMath',\r\n level: 'inline' as const,\r\n start(src: string) {\r\n return src.indexOf('$');\r\n },\r\n tokenizer(src: string) {\r\n // 匹配 $...$ 但排除 $$...$$\r\n const match = src.match(/^\\$(?!\\$)([^\\$\\n]+?)\\$(?!\\$)/);\r\n if (match) {\r\n try {\r\n const formula = match[1].trim();\r\n const html = katex.renderToString(formula, {\r\n displayMode: false,\r\n throwOnError: false,\r\n ...katexOptions\r\n });\r\n return {\r\n type: 'inlineMath',\r\n raw: match[0],\r\n html: `<span class=\"lx-ui-katex-inline\">${html}</span>`\r\n };\r\n } catch (e) {\r\n console.error('行内公式渲染错误:', e);\r\n return {\r\n type: 'inlineMath',\r\n raw: match[0],\r\n html: match[0]\r\n };\r\n }\r\n }\r\n },\r\n renderer(token: any) {\r\n return token.html || '';\r\n }\r\n }\r\n ]\r\n };\r\n}\r\n\r\n/**\r\n * 绑定代码块工具栏事件(函数重载)\r\n */\r\nexport function bindMarkdownCodeBoxEvents(): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n config: CodeBoxEventsConfig\r\n): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n container: HTMLElement | Document\r\n): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n container: HTMLElement | Document,\r\n config: CodeBoxEventsConfig\r\n): () => void;\r\n\r\n/**\r\n * 绑定代码块工具栏事件\r\n * 使用事件委托处理所有代码块的交互\r\n *\r\n * 参数判断逻辑:\r\n * 1. 先判断是否为 DOM 元素(使用 isDom 函数)\r\n * 2. 再判断是否为 config 对象\r\n *\r\n * @param containerOrConfig - 容器元素或配置对象\r\n * @param config - 事件回调配置(当第一个参数是容器时使用)\r\n * @returns 清理函数,调用可解绑事件\r\n *\r\n * @example\r\n * ```typescript\r\n * import { bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'\r\n *\r\n * // 1. 不传参数(默认 document.body)\r\n * const unbind1 = bindMarkdownCodeBoxEvents()\r\n *\r\n * // 2. 只传 config(默认 document.body)\r\n * const unbind2 = bindMarkdownCodeBoxEvents({\r\n * onCopySuccess: (text) => console.log('已复制:', text)\r\n * })\r\n *\r\n * // 3. 只传 container(使用默认配置)\r\n * const container = document.querySelector('#container')\r\n * const unbind3 = bindMarkdownCodeBoxEvents(container)\r\n *\r\n * // 4. 传 container 和 config\r\n * const unbind4 = bindMarkdownCodeBoxEvents(document.body, {\r\n * onCopySuccess: (text) => console.log('已复制:', text),\r\n * onThemeChange: (theme) => console.log('主题切换为:', theme)\r\n * })\r\n *\r\n * // 组件销毁时解绑\r\n * onUnmounted(() => unbind())\r\n * ```\r\n */\r\nexport function bindMarkdownCodeBoxEvents(\r\n containerOrConfig?: HTMLElement | Document | CodeBoxEventsConfig,\r\n config?: CodeBoxEventsConfig\r\n): () => void {\r\n if (typeof window === 'undefined') {\r\n return () => {};\r\n }\r\n\r\n // 解析参数\r\n let container: HTMLElement | Document = document.body;\r\n let eventsConfig: CodeBoxEventsConfig = {};\r\n\r\n // 判断第一个参数的类型:先判断 DOM,再判断 config\r\n if (containerOrConfig) {\r\n if (isDom(containerOrConfig)) {\r\n // 第一个参数是 container\r\n container = containerOrConfig;\r\n // 第二个参数可能是 config\r\n if (config) {\r\n eventsConfig = config;\r\n }\r\n } else {\r\n // 第一个参数是 config\r\n eventsConfig = containerOrConfig as CodeBoxEventsConfig;\r\n }\r\n }\r\n\r\n const { onCopySuccess, onCopyError, onThemeChange } = eventsConfig;\r\n\r\n /**\r\n * 点击事件处理函数\r\n */\r\n const clickHandler = async (e: Event) => {\r\n const target = e.target as HTMLElement;\r\n\r\n // 处理复制按钮\r\n const copyButton = target.closest('.lx-ui-icon-copy') as HTMLElement;\r\n if (copyButton) {\r\n const codeBox = copyButton.closest('.lx-ui-code-box') as HTMLElement;\r\n const codeElement = codeBox?.querySelector('.lx-ui-code-box-content');\r\n\r\n if (codeElement) {\r\n const text = (codeElement.textContent || '').trim();\r\n const success = await copyToClipboard(text);\r\n\r\n if (success) {\r\n onCopySuccess?.(text);\r\n } else {\r\n onCopyError?.(new Error('复制失败'));\r\n }\r\n }\r\n\r\n e.stopPropagation();\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // 处理主题切换按钮\r\n const themeButton = target.closest('.lx-ui-icon-theme') as HTMLElement;\r\n if (themeButton) {\r\n // 1. 切换 localStorage 中的主题\r\n const newTheme = toggleMarkdownCodeTheme();\r\n\r\n // 2. 更新指定容器内所有代码块的 data-theme\r\n updateMarkdownCodeBlocksTheme(newTheme);\r\n\r\n // 3. 触发回调\r\n onThemeChange?.(newTheme);\r\n\r\n e.stopPropagation();\r\n e.preventDefault();\r\n return;\r\n }\r\n };\r\n\r\n // 绑定事件\r\n container.addEventListener('click', clickHandler);\r\n\r\n // 返回清理函数\r\n return () => {\r\n container.removeEventListener('click', clickHandler);\r\n };\r\n}\r\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCO,SAAS,uBAAkC;AAChD,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAQ,aAAa,QAAQ,iBAAiB,KAAmB;AACnE;AAKO,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,OAAO,WAAW,YAAa;AACnC,eAAa,QAAQ,mBAAmB,KAAK;AAC/C;AAiBO,SAAS,0BAAqC;AACnD,QAAM,eAAe,qBAAA;AACrB,QAAM,WAAW,iBAAiB,SAAS,UAAU;AACrD,uBAAqB,QAAQ;AAC7B,SAAO;AACT;AAoBO,SAAS,8BACd,kBACA,OACM;AACN,MAAI,OAAO,WAAW,YAAa;AAGnC,MAAI;AACJ,MAAI,YAAoC,SAAS;AAEjD,MAAI,OAAO,qBAAqB,UAAU;AAExC,kBAAc;AAAA,EAChB,OAAO;AAEL,gBAAY;AACZ,kBAAc;AAAA,EAChB;AAEA,QAAM,YAAY,UAAU,iBAAiB,iBAAiB;AAC9D,YAAU,QAAQ,CAAA,QAAO;AACtB,QAAoB,aAAa,cAAc,WAAW;AAAA,EAC7D,CAAC;AACH;AAKO,SAAS,qBACd,eAA2C,IAC3C;AACA,QAAM;AAAA,IACJ,oBAAoB;AAAA,IACpB,cAAc;AAAA,IACd;AAAA,EAAA,IACE;AAEJ,QAAM,iBAAiB,IAAI,OAAO,SAAA;AAGlC,QAAM,uBAAuB,eAAe;AAC5C,iBAAe,OAAO,IAAI,QAAQ;AAChC,UAAM,OAAO,qBAAqB,KAAK,gBAAgB,GAAG,GAAG;AAC7D,WAAO,KAAK,QAAQ,QAAQ,qBAAqB;AAAA,EACnD;AAGA,QAAM,wBAAwB,eAAe;AAC7C,iBAAe,QAAQ,IAAI,QAAQ;AACjC,UAAM,OAAO,sBAAsB,KAAK,gBAAgB,GAAG,GAAG;AAC9D,WAAO,iEAAiE,IAAI;AAAA,EAC9E;AAGA,QAAM,uBAAuB,eAAe;AAC5C,iBAAe,OAAO,CAAA,UAAS;AAC7B,UAAM,OAAO,qBAAqB,KAAK,gBAAgB,KAAK;AAC5D,UAAM,QAAQ,qBAAA;AACd,UAAM,QAAO,+BAAO,SAAQ;AAG5B,UAAM,wBAAwB;AAAA;AAAA,4CAEU,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAS5C,UAAM,yBAAyB;AAAA;AAAA,4CAES,IAAI;AAAA;AAAA;AAI5C,WAAO;AAAA,gDACqC,KAAK;AAAA,UAC3C,oBAAoB,wBAAwB,sBAAsB;AAAA;AAAA,YAEhE,IAAI;AAAA;AAAA;AAAA;AAAA,EAId;AAGA,QAAM,iBAAiB,IAAI;AAAA,IACzB,gBAAgB;AAAA,MACd,YAAY;AAAA,MACZ,UAAU,MAAM,MAAM;AACpB,cAAM,WAAW,KAAK,YAAY,IAAI,IAAI,OAAO;AACjD,eAAO,KAAK,UAAU,MAAM,EAAE,SAAA,CAAU,EAAE;AAAA,MAC5C;AAAA,IAAA,CACD;AAAA,EAAA;AAIH,iBAAe,WAAW;AAAA,IACxB,UAAU;AAAA,EAAA,CACX;AAGD,MAAI,aAAa;AACf,mBAAe,IAAI,qBAAqB,YAAY,CAAC;AAAA,EACvD;AAEA,SAAO;AACT;AAOA,SAAS,qBAAqB,cAAmC;AAC/D,SAAO;AAAA,IACL,YAAY;AAAA,MACV;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,KAAa;AACjB,iBAAO,IAAI,QAAQ,IAAI;AAAA,QACzB;AAAA,QACA,UAAU,KAAa;AACrB,gBAAM,QAAQ,IAAI,MAAM,6BAA6B;AACrD,cAAI,OAAO;AACT,gBAAI;AACF,oBAAM,UAAU,MAAM,CAAC,EAAE,KAAA;AACzB,oBAAM,OAAO,MAAM,eAAe,SAAS;AAAA,gBACzC,aAAa;AAAA,gBACb,cAAc;AAAA,iBACX,aACJ;AACD,qBAAO;AAAA,gBACL,MAAM;AAAA,gBACN,KAAK,MAAM,CAAC;AAAA,gBACZ,MAAM,kCAAkC,IAAI;AAAA,cAAA;AAAA,YAEhD,SAAS,GAAG;AACV,sBAAQ,MAAM,aAAa,CAAC;AAC5B,qBAAO;AAAA,gBACL,MAAM;AAAA,gBACN,KAAK,MAAM,CAAC;AAAA,gBACZ,MAAM,MAAM,CAAC;AAAA,cAAA;AAAA,YAEjB;AAAA,UACF;AAAA,QACF;AAAA,QACA,SAAS,OAAY;AACnB,iBAAO,MAAM,QAAQ;AAAA,QACvB;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,KAAa;AACjB,iBAAO,IAAI,QAAQ,GAAG;AAAA,QACxB;AAAA,QACA,UAAU,KAAa;AAErB,gBAAM,QAAQ,IAAI,MAAM,8BAA8B;AACtD,cAAI,OAAO;AACT,gBAAI;AACF,oBAAM,UAAU,MAAM,CAAC,EAAE,KAAA;AACzB,oBAAM,OAAO,MAAM,eAAe,SAAS;AAAA,gBACzC,aAAa;AAAA,gBACb,cAAc;AAAA,iBACX,aACJ;AACD,qBAAO;AAAA,gBACL,MAAM;AAAA,gBACN,KAAK,MAAM,CAAC;AAAA,gBACZ,MAAM,oCAAoC,IAAI;AAAA,cAAA;AAAA,YAElD,SAAS,GAAG;AACV,sBAAQ,MAAM,aAAa,CAAC;AAC5B,qBAAO;AAAA,gBACL,MAAM;AAAA,gBACN,KAAK,MAAM,CAAC;AAAA,gBACZ,MAAM,MAAM,CAAC;AAAA,cAAA;AAAA,YAEjB;AAAA,UACF;AAAA,QACF;AAAA,QACA,SAAS,OAAY;AACnB,iBAAO,MAAM,QAAQ;AAAA,QACvB;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAEJ;AAuDO,SAAS,0BACd,mBACA,QACY;AACZ,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AAGA,MAAI,YAAoC,SAAS;AACjD,MAAI,eAAoC,CAAA;AAGxC,MAAI,mBAAmB;AACrB,QAAI,MAAM,iBAAiB,GAAG;AAE5B,kBAAY;AAEZ,UAAI,QAAQ;AACV,uBAAe;AAAA,MACjB;AAAA,IACF,OAAO;AAEL,qBAAe;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,EAAE,eAAe,aAAa,cAAA,IAAkB;AAKtD,QAAM,eAAe,CAAO,MAAa;AACvC,UAAM,SAAS,EAAE;AAGjB,UAAM,aAAa,OAAO,QAAQ,kBAAkB;AACpD,QAAI,YAAY;AACd,YAAM,UAAU,WAAW,QAAQ,iBAAiB;AACpD,YAAM,cAAc,mCAAS,cAAc;AAE3C,UAAI,aAAa;AACf,cAAM,QAAQ,YAAY,eAAe,IAAI,KAAA;AAC7C,cAAM,UAAU,MAAM,gBAAgB,IAAI;AAE1C,YAAI,SAAS;AACX,yDAAgB;AAAA,QAClB,OAAO;AACL,qDAAc,IAAI,MAAM,MAAM;AAAA,QAChC;AAAA,MACF;AAEA,QAAE,gBAAA;AACF,QAAE,eAAA;AACF;AAAA,IACF;AAGA,UAAM,cAAc,OAAO,QAAQ,mBAAmB;AACtD,QAAI,aAAa;AAEf,YAAM,WAAW,wBAAA;AAGjB,oCAA8B,QAAQ;AAGtC,qDAAgB;AAEhB,QAAE,gBAAA;AACF,QAAE,eAAA;AACF;AAAA,IACF;AAAA,EACF;AAGA,YAAU,iBAAiB,SAAS,YAAY;AAGhD,SAAO,MAAM;AACX,cAAU,oBAAoB,SAAS,YAAY;AAAA,EACrD;AACF;"}
1
+ {"version":3,"file":"markdown.js","sources":["../../src/markdown.ts"],"sourcesContent":["import { marked, Marked } from 'marked';\r\nimport { markedHighlight } from 'marked-highlight';\r\nimport hljs from 'highlight.js';\r\nimport katex from 'katex';\r\nimport { copyToClipboard, isDom } from './helper';\r\n\r\n/**\r\n * 代码主题类型\r\n */\r\nexport type CodeTheme = 'dark' | 'light';\r\n\r\n/**\r\n * createMarkdownParser 函数配置\r\n */\r\nexport interface CreateMarkdownParserConfig {\r\n /** 代码块工具栏是否启用(默认 true) */\r\n codeBoxToolEnable?: boolean;\r\n /** 是否启用 KaTeX 公式支持(默认 true) */\r\n katexEnable?: boolean;\r\n /** KaTeX 配置选项 */\r\n katexOptions?: katex.KatexOptions;\r\n}\r\n\r\n/**\r\n * 代码块工具栏事件配置\r\n */\r\nexport interface CodeBoxEventsConfig {\r\n /** 复制成功回调 */\r\n onCopySuccess?: (text: string) => void;\r\n /** 复制失败回调 */\r\n onCopyError?: (error: Error) => void;\r\n /** 主题切换回调 */\r\n onThemeChange?: (theme: CodeTheme) => void;\r\n}\r\n\r\n/**\r\n * 从 localStorage 获取代码主题\r\n */\r\nexport function getMarkdownCodeTheme(): CodeTheme {\r\n if (typeof window === 'undefined') return 'dark';\r\n return (localStorage.getItem('LxUIMdCodeTheme') as CodeTheme) || 'dark';\r\n}\r\n\r\n/**\r\n * 设置代码主题到 localStorage\r\n */\r\nexport function setMarkdownCodeTheme(theme: CodeTheme): void {\r\n if (typeof window === 'undefined') return;\r\n localStorage.setItem('LxUIMdCodeTheme', theme);\r\n}\r\n\r\n/**\r\n * 切换代码主题\r\n *\r\n * @returns 新主题\r\n *\r\n * @example\r\n * ```typescript\r\n * import { toggleMarkdownCodeTheme } from '@will1123/lx-ui-utils'\r\n *\r\n * // 切换主题\r\n * const newTheme = toggleMarkdownCodeTheme()\r\n * // 注意:此函数只切换 localStorage 中的主题,不会更新 DOM\r\n * // 如需更新 DOM,请配合 updateMarkdownCodeBlocksTheme\r\n * ```\r\n */\r\nexport function toggleMarkdownCodeTheme(): CodeTheme {\r\n const currentTheme = getMarkdownCodeTheme();\r\n const newTheme = currentTheme === 'dark' ? 'light' : 'dark';\r\n setMarkdownCodeTheme(newTheme);\r\n return newTheme;\r\n}\r\n\r\n/**\r\n * 更新 DOM 中所有代码块的主题\r\n *\r\n * @param themeOrContainer - 主题或容器元素(字符串表示主题,DOM 元素表示容器)\r\n * @param theme - 主题(当第一个参数是容器时使用)\r\n *\r\n * @example\r\n * ```typescript\r\n * import { updateMarkdownCodeBlocksTheme } from '@will1123/lx-ui-utils'\r\n *\r\n * // 方式 1:只传主题(更新 document.body 中的所有代码块)\r\n * updateMarkdownCodeBlocksTheme('dark')\r\n *\r\n * // 方式 2:传容器和主题\r\n * const container = document.querySelector('#markdown-container')\r\n * updateMarkdownCodeBlocksTheme(container, 'light')\r\n * ```\r\n */\r\nexport function updateMarkdownCodeBlocksTheme(\r\n themeOrContainer: CodeTheme | HTMLElement | Document,\r\n theme?: CodeTheme\r\n): void {\r\n if (typeof window === 'undefined') return;\r\n\r\n // 解析参数\r\n let actualTheme: CodeTheme;\r\n let container: HTMLElement | Document = document.body;\r\n\r\n if (typeof themeOrContainer === 'string') {\r\n // 第一个参数是 theme\r\n actualTheme = themeOrContainer;\r\n } else {\r\n // 第一个参数是 container\r\n container = themeOrContainer;\r\n actualTheme = theme as CodeTheme;\r\n }\r\n\r\n const codeBoxes = container.querySelectorAll('.lx-chat-markdown__code-box');\r\n codeBoxes.forEach(box => {\r\n (box as HTMLElement).setAttribute('data-theme', actualTheme);\r\n });\r\n}\r\n\r\n/**\r\n * 创建配置好的 Marked 实例\r\n */\r\nexport function createMarkdownParser(\r\n createConfig: CreateMarkdownParserConfig = {}\r\n) {\r\n const {\r\n codeBoxToolEnable = true,\r\n katexEnable = true,\r\n katexOptions\r\n } = createConfig;\r\n\r\n const markedRenderer = new marked.Renderer();\r\n\r\n // 重写链接渲染器 - 自动添加 target=\"_blank\"\r\n const originalLinkRenderer = markedRenderer.link;\r\n markedRenderer.link = (...arg) => {\r\n const html = originalLinkRenderer.call(markedRenderer, ...arg);\r\n return html.replace(/^<a /, '<a target=\"_blank\" ');\r\n };\r\n\r\n // 重写表格渲染器 - 包装在可滚动容器中\r\n const originalTableRenderer = markedRenderer.table;\r\n markedRenderer.table = (...arg) => {\r\n const html = originalTableRenderer.call(markedRenderer, ...arg);\r\n return `<div class=\"lx-chat-markdown__table-wrapper\"><div class=\"lx-chat-markdown__table-box\">${html}</div></div>`;\r\n };\r\n\r\n // 重写代码块渲染器 - 添加自定义样式和主题\r\n const originalCodeRenderer = markedRenderer.code;\r\n markedRenderer.code = token => {\r\n const html = originalCodeRenderer.call(markedRenderer, token);\r\n const theme = getMarkdownCodeTheme();\r\n const lang = token?.lang || '';\r\n\r\n // 代码块工具栏 HTML(启用工具栏)\r\n const codeBoxToolEnableHtml = `<div class=\"lx-chat-markdown__code-box-header\">\r\n <div class=\"lx-chat-markdown__code-box-header-left\">\r\n <span class=\"lx-chat-markdown__code-language\">${lang}</span>\r\n </div>\r\n <div class=\"lx-chat-markdown__code-box-header-right\">\r\n <i class=\"lx-icon-fuzhi lx-chat-markdown__icon-copy\" title=\"复制\"></i>\r\n <i class=\"lx-icon-taiyang lx-chat-markdown__icon-theme\" title=\"切换主题\"></i>\r\n </div>\r\n </div>`;\r\n\r\n // 代码块工具栏 HTML(禁用工具栏)\r\n const codeBoxToolDisableHtml = `<div class=\"lx-chat-markdown__code-box-header\">\r\n <div class=\"lx-chat-markdown__code-box-header-left\">\r\n <span class=\"lx-chat-markdown__code-language\">${lang}</span>\r\n </div>\r\n </div>`;\r\n\r\n return `<div class=\"lx-chat-markdown__code-box-wrapper\">\r\n <div class=\"lx-chat-markdown__code-box\" data-theme=\"${theme}\">\r\n ${codeBoxToolEnable ? codeBoxToolEnableHtml : codeBoxToolDisableHtml}\r\n <div class=\"lx-chat-markdown__code-box-content\">\r\n ${html}\r\n </div>\r\n </div>\r\n </div>`;\r\n };\r\n\r\n // 创建 Marked 实例并配置语法高亮\r\n const markdownParser = new Marked(\r\n markedHighlight({\r\n langPrefix: 'hljs language-',\r\n highlight(code, lang) {\r\n const language = hljs.getLanguage(lang) ? lang : 'plaintext';\r\n return hljs.highlight(code, { language }).value;\r\n }\r\n })\r\n );\r\n\r\n // 设置渲染器选项\r\n markdownParser.setOptions({\r\n renderer: markedRenderer\r\n });\r\n\r\n // 如果启用了 KaTeX,注册 KaTeX 扩展\r\n if (katexEnable) {\r\n markdownParser.use(createKatexExtension(katexOptions));\r\n }\r\n\r\n return markdownParser;\r\n}\r\n\r\n/**\r\n * 创建 KaTeX Marked 扩展\r\n * @param katexOptions - KaTeX 配置选项\r\n * @returns Marked 扩展对象\r\n */\r\nfunction createKatexExtension(katexOptions?: katex.KatexOptions) {\r\n return {\r\n extensions: [\r\n {\r\n name: 'blockMath',\r\n level: 'block' as const,\r\n start(src: string) {\r\n return src.indexOf('$$');\r\n },\r\n tokenizer(src: string) {\r\n const match = src.match(/^\\$\\$([\\s\\S]*?)\\$\\$(?:\\n|$)/);\r\n if (match) {\r\n try {\r\n const formula = match[1].trim();\r\n const html = katex.renderToString(formula, {\r\n displayMode: true,\r\n throwOnError: false,\r\n ...katexOptions\r\n });\r\n return {\r\n type: 'blockMath',\r\n raw: match[0],\r\n html: `<div class=\"lx-chat-markdown__katex-block\">${html}</div>`\r\n };\r\n } catch (e) {\r\n console.error('块级公式渲染错误:', e);\r\n return {\r\n type: 'blockMath',\r\n raw: match[0],\r\n html: match[0]\r\n };\r\n }\r\n }\r\n },\r\n renderer(token: any) {\r\n return token.html || '';\r\n }\r\n },\r\n {\r\n name: 'inlineMath',\r\n level: 'inline' as const,\r\n start(src: string) {\r\n return src.indexOf('$');\r\n },\r\n tokenizer(src: string) {\r\n // 匹配 $...$ 但排除 $$...$$\r\n const match = src.match(/^\\$(?!\\$)([^\\$\\n]+?)\\$(?!\\$)/);\r\n if (match) {\r\n try {\r\n const formula = match[1].trim();\r\n const html = katex.renderToString(formula, {\r\n displayMode: false,\r\n throwOnError: false,\r\n ...katexOptions\r\n });\r\n return {\r\n type: 'inlineMath',\r\n raw: match[0],\r\n html: `<span class=\"lx-chat-markdown__katex-inline\">${html}</span>`\r\n };\r\n } catch (e) {\r\n console.error('行内公式渲染错误:', e);\r\n return {\r\n type: 'inlineMath',\r\n raw: match[0],\r\n html: match[0]\r\n };\r\n }\r\n }\r\n },\r\n renderer(token: any) {\r\n return token.html || '';\r\n }\r\n }\r\n ]\r\n };\r\n}\r\n\r\n/**\r\n * 绑定代码块工具栏事件(函数重载)\r\n */\r\nexport function bindMarkdownCodeBoxEvents(): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n config: CodeBoxEventsConfig\r\n): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n container: HTMLElement | Document\r\n): () => void;\r\nexport function bindMarkdownCodeBoxEvents(\r\n container: HTMLElement | Document,\r\n config: CodeBoxEventsConfig\r\n): () => void;\r\n\r\n/**\r\n * 绑定代码块工具栏事件\r\n * 使用事件委托处理所有代码块的交互\r\n *\r\n * 参数判断逻辑:\r\n * 1. 先判断是否为 DOM 元素(使用 isDom 函数)\r\n * 2. 再判断是否为 config 对象\r\n *\r\n * @param containerOrConfig - 容器元素或配置对象\r\n * @param config - 事件回调配置(当第一个参数是容器时使用)\r\n * @returns 清理函数,调用可解绑事件\r\n *\r\n * @example\r\n * ```typescript\r\n * import { bindMarkdownCodeBoxEvents } from '@will1123/lx-ui-utils'\r\n *\r\n * // 1. 不传参数(默认 document.body)\r\n * const unbind1 = bindMarkdownCodeBoxEvents()\r\n *\r\n * // 2. 只传 config(默认 document.body)\r\n * const unbind2 = bindMarkdownCodeBoxEvents({\r\n * onCopySuccess: (text) => console.log('已复制:', text)\r\n * })\r\n *\r\n * // 3. 只传 container(使用默认配置)\r\n * const container = document.querySelector('#container')\r\n * const unbind3 = bindMarkdownCodeBoxEvents(container)\r\n *\r\n * // 4. 传 container 和 config\r\n * const unbind4 = bindMarkdownCodeBoxEvents(document.body, {\r\n * onCopySuccess: (text) => console.log('已复制:', text),\r\n * onThemeChange: (theme) => console.log('主题切换为:', theme)\r\n * })\r\n *\r\n * // 组件销毁时解绑\r\n * onUnmounted(() => unbind())\r\n * ```\r\n */\r\nexport function bindMarkdownCodeBoxEvents(\r\n containerOrConfig?: HTMLElement | Document | CodeBoxEventsConfig,\r\n config?: CodeBoxEventsConfig\r\n): () => void {\r\n if (typeof window === 'undefined') {\r\n return () => {};\r\n }\r\n\r\n // 解析参数\r\n let container: HTMLElement | Document = document.body;\r\n let eventsConfig: CodeBoxEventsConfig = {};\r\n\r\n // 判断第一个参数的类型:先判断 DOM,再判断 config\r\n if (containerOrConfig) {\r\n if (isDom(containerOrConfig)) {\r\n // 第一个参数是 container\r\n container = containerOrConfig;\r\n // 第二个参数可能是 config\r\n if (config) {\r\n eventsConfig = config;\r\n }\r\n } else {\r\n // 第一个参数是 config\r\n eventsConfig = containerOrConfig as CodeBoxEventsConfig;\r\n }\r\n }\r\n\r\n const { onCopySuccess, onCopyError, onThemeChange } = eventsConfig;\r\n\r\n /**\r\n * 点击事件处理函数\r\n */\r\n const clickHandler = async (e: Event) => {\r\n const target = e.target as HTMLElement;\r\n\r\n // 处理复制按钮\r\n const copyButton = target.closest('.lx-chat-markdown__icon-copy') as HTMLElement;\r\n if (copyButton) {\r\n const codeBox = copyButton.closest('.lx-chat-markdown__code-box') as HTMLElement;\r\n const codeElement = codeBox?.querySelector('.lx-chat-markdown__code-box-content');\r\n\r\n if (codeElement) {\r\n const text = (codeElement.textContent || '').trim();\r\n const success = await copyToClipboard(text);\r\n\r\n if (success) {\r\n onCopySuccess?.(text);\r\n } else {\r\n onCopyError?.(new Error('复制失败'));\r\n }\r\n }\r\n\r\n e.stopPropagation();\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // 处理主题切换按钮\r\n const themeButton = target.closest('.lx-chat-markdown__icon-theme') as HTMLElement;\r\n if (themeButton) {\r\n // 1. 切换 localStorage 中的主题\r\n const newTheme = toggleMarkdownCodeTheme();\r\n\r\n // 2. 更新指定容器内所有代码块的 data-theme\r\n updateMarkdownCodeBlocksTheme(newTheme);\r\n\r\n // 3. 触发回调\r\n onThemeChange?.(newTheme);\r\n\r\n e.stopPropagation();\r\n e.preventDefault();\r\n return;\r\n }\r\n };\r\n\r\n // 绑定事件\r\n container.addEventListener('click', clickHandler);\r\n\r\n // 返回清理函数\r\n return () => {\r\n container.removeEventListener('click', clickHandler);\r\n };\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { createMarkdownParser } from "./markdown.js";
2
+ function getBaseMessageOption() {
3
+ return {
4
+ // 用户问题字段名
5
+ questionKey: "question",
6
+ // 思考过程文本字段名
7
+ thinkingKey: "thinking",
8
+ // 正文文本字段名
9
+ answerKey: "answer",
10
+ // 异常信息字段名
11
+ errorKey: "error",
12
+ // 推荐问题列表字段名
13
+ recommendQuestionKey: "recommand_question",
14
+ // 是否显示思考过程
15
+ thinkingEnable: true,
16
+ // 正文文本是否渲染为HTML
17
+ answerRenderHtmlEnable: true,
18
+ // 回复过程中是否显示加载中图标
19
+ answerLoadingEnable: true,
20
+ // 图表字段名
21
+ chartKey: "chart",
22
+ // 是否显示图表
23
+ chartEnable: true,
24
+ // 是否显示图表工具栏
25
+ chartToolBarEnable: true,
26
+ // SQL字段名
27
+ sqlKey: "sql",
28
+ // 图表配色
29
+ chartColors: [
30
+ "#2EC7C9",
31
+ "#165dff",
32
+ "#5AB1EF",
33
+ "#B6A2DE",
34
+ "#FFB980",
35
+ "#D87A80",
36
+ "#001852"
37
+ ],
38
+ // 是否自定义设置图表 options
39
+ customSetChartOptions: null,
40
+ // 是否自定义设置图表 - 表格渲染数据
41
+ customSetChartTableColumns: null,
42
+ // 自定义表格类名
43
+ chartTableClassName: "",
44
+ // 是否显示推荐问题列表
45
+ recommendQuestionEnable: true,
46
+ // 消息底部的工具栏是否显示
47
+ messageToolbarEnable: true,
48
+ // 是否允许消息反馈
49
+ feedbackEnable: true,
50
+ // 是否允许自定义消息反馈
51
+ customFeedbackEnable: true,
52
+ // 自定义消息反馈 - 点赞可选项
53
+ customFeedbackLikeOptions: [
54
+ { label: "内容准确", value: "内容准确" },
55
+ { label: "易于理解", value: "易于理解" },
56
+ { label: "内容完善", value: "内容完善" }
57
+ ],
58
+ // 自定义消息反馈 - 点踩可选项
59
+ customFeedbackDisLikeOptions: [
60
+ { label: "有害/不安全", value: "有害/不安全" },
61
+ { label: "信息虚假", value: "信息虚假" },
62
+ { label: "没有帮助", value: "没有帮助" },
63
+ { label: "隐私相关", value: "隐私相关" }
64
+ ],
65
+ // 用户头像
66
+ userAvatar: "",
67
+ // 大模型回复头像
68
+ assistantAvatar: "",
69
+ // 是否显示头像
70
+ avatarEnable: true,
71
+ // markdown 解析器
72
+ markdownParser: createMarkdownParser({
73
+ katexEnable: true,
74
+ codeBoxToolEnable: true
75
+ })
76
+ };
77
+ }
78
+ export {
79
+ getBaseMessageOption
80
+ };
81
+ //# sourceMappingURL=message-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-config.js","sources":["../../src/message-config.ts"],"sourcesContent":["import { createMarkdownParser } from './markdown';\r\n\r\n/**\r\n * 获取消息基础配置\r\n * 用于 chat-message-list 及其子组件的 inject 默认值\r\n *\r\n * @returns 消息配置对象\r\n *\r\n * @example\r\n * ```typescript\r\n * import { getBaseMessageOption } from '@will1123/lx-ui-utils/message-config'\r\n *\r\n * const option = getBaseMessageOption()\r\n * console.log(option.questionKey) // 'question'\r\n * ```\r\n */\r\nexport function getBaseMessageOption() {\r\n return {\r\n // 用户问题字段名\r\n questionKey: 'question',\r\n // 思考过程文本字段名\r\n thinkingKey: 'thinking',\r\n // 正文文本字段名\r\n answerKey: 'answer',\r\n // 异常信息字段名\r\n errorKey: 'error',\r\n // 推荐问题列表字段名\r\n recommendQuestionKey: 'recommand_question',\r\n // 是否显示思考过程\r\n thinkingEnable: true,\r\n // 正文文本是否渲染为HTML\r\n answerRenderHtmlEnable: true,\r\n // 回复过程中是否显示加载中图标\r\n answerLoadingEnable: true,\r\n // 图表字段名\r\n chartKey: 'chart',\r\n // 是否显示图表\r\n chartEnable: true,\r\n // 是否显示图表工具栏\r\n chartToolBarEnable: true,\r\n // SQL字段名\r\n sqlKey: 'sql',\r\n // 图表配色\r\n chartColors: [\r\n '#2EC7C9',\r\n '#165dff',\r\n '#5AB1EF',\r\n '#B6A2DE',\r\n '#FFB980',\r\n '#D87A80',\r\n '#001852'\r\n ],\r\n // 是否自定义设置图表 options\r\n customSetChartOptions: null,\r\n // 是否自定义设置图表 - 表格渲染数据\r\n customSetChartTableColumns: null,\r\n // 自定义表格类名\r\n chartTableClassName: '',\r\n // 是否显示推荐问题列表\r\n recommendQuestionEnable: true,\r\n // 消息底部的工具栏是否显示\r\n messageToolbarEnable: true,\r\n // 是否允许消息反馈\r\n feedbackEnable: true,\r\n // 是否允许自定义消息反馈\r\n customFeedbackEnable: true,\r\n // 自定义消息反馈 - 点赞可选项\r\n customFeedbackLikeOptions: [\r\n { label: '内容准确', value: '内容准确' },\r\n { label: '易于理解', value: '易于理解' },\r\n { label: '内容完善', value: '内容完善' }\r\n ],\r\n // 自定义消息反馈 - 点踩可选项\r\n customFeedbackDisLikeOptions: [\r\n { label: '有害/不安全', value: '有害/不安全' },\r\n { label: '信息虚假', value: '信息虚假' },\r\n { label: '没有帮助', value: '没有帮助' },\r\n { label: '隐私相关', value: '隐私相关' }\r\n ],\r\n // 用户头像\r\n userAvatar: '',\r\n // 大模型回复头像\r\n assistantAvatar: '',\r\n // 是否显示头像\r\n avatarEnable: true,\r\n // markdown 解析器\r\n markdownParser: createMarkdownParser({\r\n katexEnable: true,\r\n codeBoxToolEnable: true\r\n })\r\n };\r\n}\r\n"],"names":[],"mappings":";AAgBO,SAAS,uBAAuB;AACrC,SAAO;AAAA;AAAA,IAEL,aAAa;AAAA;AAAA,IAEb,aAAa;AAAA;AAAA,IAEb,WAAW;AAAA;AAAA,IAEX,UAAU;AAAA;AAAA,IAEV,sBAAsB;AAAA;AAAA,IAEtB,gBAAgB;AAAA;AAAA,IAEhB,wBAAwB;AAAA;AAAA,IAExB,qBAAqB;AAAA;AAAA,IAErB,UAAU;AAAA;AAAA,IAEV,aAAa;AAAA;AAAA,IAEb,oBAAoB;AAAA;AAAA,IAEpB,QAAQ;AAAA;AAAA,IAER,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA;AAAA,IAGF,uBAAuB;AAAA;AAAA,IAEvB,4BAA4B;AAAA;AAAA,IAE5B,qBAAqB;AAAA;AAAA,IAErB,yBAAyB;AAAA;AAAA,IAEzB,sBAAsB;AAAA;AAAA,IAEtB,gBAAgB;AAAA;AAAA,IAEhB,sBAAsB;AAAA;AAAA,IAEtB,2BAA2B;AAAA,MACzB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,MACxB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,MACxB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,IAAO;AAAA;AAAA,IAGjC,8BAA8B;AAAA,MAC5B,EAAE,OAAO,UAAU,OAAO,SAAA;AAAA,MAC1B,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,MACxB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,MACxB,EAAE,OAAO,QAAQ,OAAO,OAAA;AAAA,IAAO;AAAA;AAAA,IAGjC,YAAY;AAAA;AAAA,IAEZ,iBAAiB;AAAA;AAAA,IAEjB,cAAc;AAAA;AAAA,IAEd,gBAAgB,qBAAqB;AAAA,MACnC,aAAa;AAAA,MACb,mBAAmB;AAAA,IAAA,CACpB;AAAA,EAAA;AAEL;"}
package/dist/es/sse.js CHANGED
@@ -37,14 +37,22 @@ var __async = (__this, __arguments, generator) => {
37
37
  import { fetchEventSource } from "@microsoft/fetch-event-source";
38
38
  function sse(config, handlers) {
39
39
  return __async(this, null, function* () {
40
- const { url, method = "POST", headers = {}, params, signal, token, openWhenHidden = true } = config;
40
+ const {
41
+ url,
42
+ method = "POST",
43
+ headers = {},
44
+ params,
45
+ signal,
46
+ token,
47
+ openWhenHidden = true
48
+ } = config;
41
49
  const requestHeaders = __spreadValues({
42
50
  "Content-Type": "application/json;charset=utf-8"
43
51
  }, headers);
44
52
  if (token) {
45
53
  requestHeaders["Authorization"] = token;
46
54
  }
47
- yield fetchEventSource(url, {
55
+ return fetchEventSource(url, {
48
56
  method,
49
57
  headers: requestHeaders,
50
58
  body: JSON.stringify(params),
@@ -1 +1 @@
1
- {"version":3,"file":"sse.js","sources":["../../src/sse.ts"],"sourcesContent":["import { fetchEventSource, type EventSourceMessage } from '@microsoft/fetch-event-source'\r\n\r\n// 重新导出 EventSourceMessage 类型\r\nexport type { EventSourceMessage }\r\n\r\n/**\r\n * SSE 请求配置\r\n */\r\nexport interface SSEConfig {\r\n /** 请求 URL */\r\n url: string\r\n /** 请求方法(默认 'POST') */\r\n method?: 'GET' | 'POST'\r\n /** 请求头 */\r\n headers?: Record<string, string>\r\n /** 请求参数(对象类型) */\r\n params?: object\r\n /** AbortController 的 signal(用于取消请求) */\r\n signal?: AbortSignal\r\n /** 认证 Token */\r\n token?: string\r\n /** 页面隐藏时是否保持连接(默认 true) */\r\n openWhenHidden?: boolean\r\n}\r\n\r\n/**\r\n * SSE 事件处理器\r\n */\r\nexport interface SSEHandlers {\r\n /** 连接打开时触发(可验证响应) */\r\n onOpen?: (response: Response) => void | Promise<void>;\r\n /** 接收到消息时触发 */\r\n onMessage?: (message: EventSourceMessage) => void;\r\n /** 连接关闭时触发 */\r\n onClose?: () => void;\r\n /** 发生错误时触发 */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * 发起 SSE 请求\r\n *\r\n * @example\r\n * ```ts\r\n * // 基础用法\r\n * await sse({\r\n * url: '/api/stream',\r\n * params: { prompt: 'hello' }\r\n * }, {\r\n * onMessage(msg) {\r\n * console.log(msg.data)\r\n * }\r\n * })\r\n *\r\n * // 使用 Token 认证\r\n * await sse({\r\n * url: '/api/stream',\r\n * token: 'Bearer your-token',\r\n * params: { prompt: 'hello' }\r\n * }, {\r\n * onOpen(response) {\r\n * console.log('连接已打开')\r\n * },\r\n * onMessage(msg) {\r\n * console.log(msg.data)\r\n * },\r\n * onError(err) {\r\n * console.error(err)\r\n * }\r\n * })\r\n * ```\r\n */\r\nexport async function sse(\r\n config: SSEConfig,\r\n handlers: SSEHandlers\r\n): Promise<void> {\r\n const { url, method = 'POST', headers = {}, params, signal, token, openWhenHidden = true } = config;\r\n\r\n // 构建请求头,自动添加 Authorization\r\n const requestHeaders: Record<string, string> = {\r\n 'Content-Type': 'application/json;charset=utf-8',\r\n ...headers\r\n };\r\n\r\n if (token) {\r\n requestHeaders['Authorization'] = token;\r\n }\r\n\r\n await fetchEventSource(url, {\r\n method,\r\n headers: requestHeaders,\r\n body: JSON.stringify(params),\r\n signal,\r\n openWhenHidden,\r\n\r\n async onopen(response) {\r\n await handlers.onOpen?.(response);\r\n },\r\n\r\n onmessage(msg) {\r\n handlers.onMessage?.(msg);\r\n },\r\n\r\n onclose() {\r\n handlers.onClose?.();\r\n },\r\n\r\n onerror(err) {\r\n handlers.onError?.(err);\r\n throw err;\r\n }\r\n });\r\n}\r\n"],"names":["_a"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAsB,IACpB,QACA,UACe;AAAA;AACf,UAAM,EAAE,KAAK,SAAS,QAAQ,UAAU,CAAA,GAAI,QAAQ,QAAQ,OAAO,iBAAiB,KAAA,IAAS;AAG7F,UAAM,iBAAyC;AAAA,MAC7C,gBAAgB;AAAA,OACb;AAGL,QAAI,OAAO;AACT,qBAAe,eAAe,IAAI;AAAA,IACpC;AAEA,UAAM,iBAAiB,KAAK;AAAA,MAC1B;AAAA,MACA,SAAS;AAAA,MACT,MAAM,KAAK,UAAU,MAAM;AAAA,MAC3B;AAAA,MACA;AAAA,MAEM,OAAO,UAAU;AAAA;;AACrB,iBAAMA,MAAA,SAAS,WAAT,gBAAAA,IAAA,eAAkB;AAAA,QAC1B;AAAA;AAAA,MAEA,UAAU,KAAK;;AACb,SAAAA,MAAA,SAAS,cAAT,gBAAAA,IAAA,eAAqB;AAAA,MACvB;AAAA,MAEA,UAAU;;AACR,SAAAA,MAAA,SAAS,YAAT,gBAAAA,IAAA;AAAA,MACF;AAAA,MAEA,QAAQ,KAAK;;AACX,SAAAA,MAAA,SAAS,YAAT,gBAAAA,IAAA,eAAmB;AACnB,cAAM;AAAA,MACR;AAAA,IAAA,CACD;AAAA,EACH;AAAA;"}
1
+ {"version":3,"file":"sse.js","sources":["../../src/sse.ts"],"sourcesContent":["import {\r\n fetchEventSource,\r\n type EventSourceMessage\r\n} from '@microsoft/fetch-event-source';\r\n\r\n// 重新导出 EventSourceMessage 类型\r\nexport type { EventSourceMessage };\r\n\r\n/**\r\n * SSE 请求配置\r\n */\r\nexport interface SSEConfig {\r\n /** 请求 URL */\r\n url: string;\r\n /** 请求方法(默认 'POST') */\r\n method?: 'GET' | 'POST';\r\n /** 请求头 */\r\n headers?: Record<string, string>;\r\n /** 请求参数(对象类型) */\r\n params?: object;\r\n /** AbortController 的 signal(用于取消请求) */\r\n signal?: AbortSignal;\r\n /** 认证 Token */\r\n token?: string;\r\n /** 页面隐藏时是否保持连接(默认 true) */\r\n openWhenHidden?: boolean;\r\n}\r\n\r\n/**\r\n * SSE 事件处理器\r\n */\r\nexport interface SSEHandlers {\r\n /** 连接打开时触发(可验证响应) */\r\n onOpen?: (response: Response) => void | Promise<void>;\r\n /** 接收到消息时触发 */\r\n onMessage?: (message: EventSourceMessage) => void;\r\n /** 连接关闭时触发 */\r\n onClose?: () => void;\r\n /** 发生错误时触发 */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * 发起 SSE 请求\r\n *\r\n * @example\r\n * ```ts\r\n * // 基础用法\r\n * await sse({\r\n * url: '/api/stream',\r\n * params: { prompt: 'hello' }\r\n * }, {\r\n * onMessage(msg) {\r\n * console.log(msg.data)\r\n * }\r\n * })\r\n *\r\n * // 使用 Token 认证\r\n * await sse({\r\n * url: '/api/stream',\r\n * token: 'Bearer your-token',\r\n * params: { prompt: 'hello' }\r\n * }, {\r\n * onOpen(response) {\r\n * console.log('连接已打开')\r\n * },\r\n * onMessage(msg) {\r\n * console.log(msg.data)\r\n * },\r\n * onError(err) {\r\n * console.error(err)\r\n * }\r\n * })\r\n * ```\r\n */\r\nexport async function sse(\r\n config: SSEConfig,\r\n handlers: SSEHandlers\r\n): Promise<void> {\r\n const {\r\n url,\r\n method = 'POST',\r\n headers = {},\r\n params,\r\n signal,\r\n token,\r\n openWhenHidden = true\r\n } = config;\r\n\r\n // 构建请求头,自动添加 Authorization\r\n const requestHeaders: Record<string, string> = {\r\n 'Content-Type': 'application/json;charset=utf-8',\r\n ...headers\r\n };\r\n\r\n if (token) {\r\n requestHeaders['Authorization'] = token;\r\n }\r\n\r\n return fetchEventSource(url, {\r\n method,\r\n headers: requestHeaders,\r\n body: JSON.stringify(params),\r\n signal,\r\n openWhenHidden,\r\n\r\n async onopen(response) {\r\n await handlers.onOpen?.(response);\r\n },\r\n\r\n onmessage(msg) {\r\n handlers.onMessage?.(msg);\r\n },\r\n\r\n onclose() {\r\n handlers.onClose?.();\r\n },\r\n\r\n onerror(err) {\r\n handlers.onError?.(err);\r\n throw err;\r\n }\r\n });\r\n}\r\n"],"names":["_a"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,SAAsB,IACpB,QACA,UACe;AAAA;AACf,UAAM;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT,UAAU,CAAA;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA,iBAAiB;AAAA,IAAA,IACf;AAGJ,UAAM,iBAAyC;AAAA,MAC7C,gBAAgB;AAAA,OACb;AAGL,QAAI,OAAO;AACT,qBAAe,eAAe,IAAI;AAAA,IACpC;AAEA,WAAO,iBAAiB,KAAK;AAAA,MAC3B;AAAA,MACA,SAAS;AAAA,MACT,MAAM,KAAK,UAAU,MAAM;AAAA,MAC3B;AAAA,MACA;AAAA,MAEM,OAAO,UAAU;AAAA;;AACrB,iBAAMA,MAAA,SAAS,WAAT,gBAAAA,IAAA,eAAkB;AAAA,QAC1B;AAAA;AAAA,MAEA,UAAU,KAAK;;AACb,SAAAA,MAAA,SAAS,cAAT,gBAAAA,IAAA,eAAqB;AAAA,MACvB;AAAA,MAEA,UAAU;;AACR,SAAAA,MAAA,SAAS,YAAT,gBAAAA,IAAA;AAAA,MACF;AAAA,MAEA,QAAQ,KAAK;;AACX,SAAAA,MAAA,SAAS,YAAT,gBAAAA,IAAA,eAAmB;AACnB,cAAM;AAAA,MACR;AAAA,IAAA,CACD;AAAA,EACH;AAAA;"}
@@ -20,6 +20,62 @@ var __async = (__this, __arguments, generator) => {
20
20
  });
21
21
  };
22
22
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
23
+ const uuid = require("uuid");
24
+ const fileSaver = require("file-saver");
25
+ const XLSX = require("xlsx");
26
+ function _interopNamespaceDefault(e) {
27
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
28
+ if (e) {
29
+ for (const k in e) {
30
+ if (k !== "default") {
31
+ const d = Object.getOwnPropertyDescriptor(e, k);
32
+ Object.defineProperty(n, k, d.get ? d : {
33
+ enumerable: true,
34
+ get: () => e[k]
35
+ });
36
+ }
37
+ }
38
+ }
39
+ n.default = e;
40
+ return Object.freeze(n);
41
+ }
42
+ const XLSX__namespace = /* @__PURE__ */ _interopNamespaceDefault(XLSX);
43
+ function generateUniqueId() {
44
+ return uuid.v4();
45
+ }
46
+ function saveAs(data, filename) {
47
+ const defaultName = filename || (data instanceof File ? data.name : "download");
48
+ fileSaver.saveAs(data, defaultName);
49
+ }
50
+ function generateExcel(data, options) {
51
+ const {
52
+ sheetName = "Sheet1",
53
+ headers
54
+ } = options || {};
55
+ const worksheet = XLSX__namespace.utils.json_to_sheet(data, {
56
+ header: headers ? Object.keys(headers) : void 0
57
+ });
58
+ if (headers && worksheet["!ref"]) {
59
+ const range = XLSX__namespace.utils.decode_range(worksheet["!ref"]);
60
+ const headerRow = range.s.r;
61
+ Object.keys(headers).forEach((key, index) => {
62
+ const cellAddress = XLSX__namespace.utils.encode_cell({
63
+ r: headerRow,
64
+ c: index
65
+ });
66
+ worksheet[cellAddress].v = headers[key];
67
+ });
68
+ }
69
+ const workbook = XLSX__namespace.utils.book_new();
70
+ XLSX__namespace.utils.book_append_sheet(workbook, worksheet, sheetName);
71
+ const excelBuffer = XLSX__namespace.write(workbook, {
72
+ bookType: "xlsx",
73
+ type: "array"
74
+ });
75
+ return new Blob([excelBuffer], {
76
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
77
+ });
78
+ }
23
79
  function copyToClipboard(text) {
24
80
  return __async(this, null, function* () {
25
81
  if (typeof window === "undefined") return false;
@@ -55,5 +111,8 @@ function isDom(val) {
55
111
  return val instanceof HTMLElement || val instanceof Document;
56
112
  }
57
113
  exports.copyToClipboard = copyToClipboard;
114
+ exports.generateExcel = generateExcel;
115
+ exports.generateUniqueId = generateUniqueId;
58
116
  exports.isDom = isDom;
117
+ exports.saveAs = saveAs;
59
118
  //# sourceMappingURL=helper.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"helper.js","sources":["../../src/helper.ts"],"sourcesContent":["/**\r\n * 复制文本到剪贴板\r\n *\r\n * @param text - 要复制的文本\r\n * @returns Promise<boolean> - 成功返回 true,失败返回 false\r\n *\r\n * @example\r\n * ```typescript\r\n * import { copyToClipboard } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const success = await copyToClipboard('Hello World')\r\n * if (success) {\r\n * console.log('复制成功')\r\n * } else {\r\n * console.log('复制失败')\r\n * }\r\n *\r\n * // 复制代码\r\n * const code = 'console.log(\"Hello World\")'\r\n * await copyToClipboard(code)\r\n *\r\n * // 复制 HTML 内容\r\n * const html = '<div>Hello</div>'\r\n * await copyToClipboard(html)\r\n * ```\r\n */\r\nexport async function copyToClipboard(text: string): Promise<boolean> {\r\n if (typeof window === 'undefined') return false;\r\n\r\n // 使用现代 Clipboard API\r\n if (navigator.clipboard && navigator.clipboard.writeText) {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n return false;\r\n }\r\n }\r\n\r\n // 降级方案:使用 document.execCommand\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n textArea.style.position = 'fixed';\r\n textArea.style.opacity = '0';\r\n textArea.style.left = '-9999px';\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n try {\r\n const successful = document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n return successful;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n document.body.removeChild(textArea);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 判断值是否为 DOM 元素(HTMLElement 或 Document)\r\n *\r\n * @param val - 要判断的值\r\n * @returns 是否为 DOM 元素\r\n *\r\n * @example\r\n * ```typescript\r\n * import { isDom } from '@will1123/lx-ui-utils'\r\n *\r\n * isDom(document.body) // true\r\n * isDom(document) // true\r\n * isDom({}) // false\r\n * isDom('div') // false\r\n * ```\r\n */\r\nexport function isDom(val: unknown): val is HTMLElement | Document {\r\n return val instanceof HTMLElement || val instanceof Document;\r\n}\r\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAsB,gBAAgB,MAAgC;AAAA;AACpE,QAAI,OAAO,WAAW,YAAa,QAAO;AAG1C,QAAI,UAAU,aAAa,UAAU,UAAU,WAAW;AACxD,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,IAAI;AACxC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,SAAS,KAAK;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,SAAS,cAAc,UAAU;AAClD,aAAS,QAAQ;AACjB,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,UAAU;AACzB,aAAS,MAAM,OAAO;AACtB,aAAS,KAAK,YAAY,QAAQ;AAClC,aAAS,MAAA;AACT,aAAS,OAAA;AAET,QAAI;AACF,YAAM,aAAa,SAAS,YAAY,MAAM;AAC9C,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,SAAS,KAAK;AAC5B,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAkBO,SAAS,MAAM,KAA6C;AACjE,SAAO,eAAe,eAAe,eAAe;AACtD;;;"}
1
+ {"version":3,"file":"helper.js","sources":["../../src/helper.ts"],"sourcesContent":["import { v4 as uuidv4 } from 'uuid';\r\nimport { saveAs as fileSaveAs } from 'file-saver';\r\nimport * as XLSX from 'xlsx';\r\n\r\n/**\r\n * 生成唯一 ID\r\n *\r\n * 基于 UUID v4 生成唯一标识符\r\n *\r\n * @returns 唯一 ID 字符串(格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)\r\n *\r\n * @example\r\n * ```typescript\r\n * import { generateUniqueId } from '@will1123/lx-ui-utils'\r\n *\r\n * // 生成唯一 ID\r\n * const id = generateUniqueId()\r\n * console.log(id) // 输出:'550e8400-e29b-41d4-a716-446655440000'\r\n *\r\n * // 用于生成唯一 key\r\n * const items = [1, 2, 3].map(item => ({\r\n * id: generateUniqueId(),\r\n * data: item\r\n * }))\r\n *\r\n * // 用于生成临时标识\r\n * const tempId = generateUniqueId()\r\n * ```\r\n */\r\nexport function generateUniqueId(): string {\r\n return uuidv4();\r\n}\r\n\r\n/**\r\n * 保存文件到本地\r\n *\r\n * 基于 file-saver 实现文件保存功能,支持多种数据类型\r\n *\r\n * @param data - 要保存的数据,支持 Blob、File\r\n * @param filename - 文件名\r\n *\r\n * @example\r\n * ```typescript\r\n * import { saveAs } from '@will1123/lx-ui-utils'\r\n *\r\n * // 保存 Blob\r\n * const blob = new Blob(['Hello'], { type: 'text/plain;charset=utf-8' })\r\n * saveAs(blob, 'greeting.txt')\r\n *\r\n * // 保存文件(File 对象)\r\n * saveAs(file, 'backup.txt')\r\n *\r\n * // 不指定文件名(使用默认)\r\n * saveAs(blob)\r\n * ```\r\n */\r\nexport function saveAs(data: Blob | File, filename?: string): void {\r\n const defaultName =\r\n filename || (data instanceof File ? data.name : 'download');\r\n\r\n fileSaveAs(data, defaultName);\r\n}\r\n\r\n/**\r\n * 生成 Excel 文件\r\n *\r\n * 基于 xlsx 库实现 Excel 文件生成功能,支持自定义列名和工作表名称\r\n *\r\n * @param data - 要转换的数据数组(对象数组)\r\n * @param options - 生成选项\r\n * @param options.sheetName - 工作表名称(默认 'Sheet1')\r\n * @param options.headers - 自定义列名(key: 字段名, value: 列标题)\r\n * @returns Excel 文件的 Blob 对象\r\n *\r\n * @example\r\n * ```typescript\r\n * import { generateExcel, saveAs } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const data = [\r\n * { name: '张三', age: 25, city: '北京' },\r\n * { name: '李四', age: 30, city: '上海' }\r\n * ]\r\n * const blob = generateExcel(data)\r\n * saveAs(blob, '用户数据.xlsx')\r\n *\r\n * // 指定工作表名称\r\n * const blob = generateExcel(data, {\r\n * sheetName: '用户列表'\r\n * })\r\n *\r\n * // 自定义列名\r\n * const blob = generateExcel(data, {\r\n * headers: {\r\n * name: '姓名',\r\n * age: '年龄',\r\n * city: '城市'\r\n * }\r\n * })\r\n * saveAs(blob, '用户数据.xlsx')\r\n * ```\r\n */\r\nexport function generateExcel(\r\n data: Record<string, any>[],\r\n options?: {\r\n sheetName?: string;\r\n headers?: Record<string, string>;\r\n }\r\n): Blob {\r\n const {\r\n sheetName = 'Sheet1',\r\n headers\r\n } = options || {};\r\n\r\n // 创建工作表\r\n const worksheet = XLSX.utils.json_to_sheet(data, {\r\n header: headers ? Object.keys(headers) : undefined\r\n });\r\n\r\n // 自定义表头\r\n if (headers && worksheet['!ref']) {\r\n const range = XLSX.utils.decode_range(worksheet['!ref']);\r\n const headerRow = range.s.r;\r\n\r\n Object.keys(headers).forEach((key, index) => {\r\n const cellAddress = XLSX.utils.encode_cell({\r\n r: headerRow,\r\n c: index\r\n });\r\n worksheet[cellAddress].v = headers[key];\r\n });\r\n }\r\n\r\n // 创建工作簿\r\n const workbook = XLSX.utils.book_new();\r\n XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);\r\n\r\n // 写入文件并返回 Blob\r\n const excelBuffer = XLSX.write(workbook, {\r\n bookType: 'xlsx',\r\n type: 'array'\r\n });\r\n\r\n return new Blob([excelBuffer], {\r\n type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'\r\n });\r\n}\r\n\r\n/**\r\n * 复制文本到剪贴板\r\n *\r\n * @param text - 要复制的文本\r\n * @returns Promise<boolean> - 成功返回 true,失败返回 false\r\n *\r\n * @example\r\n * ```typescript\r\n * import { copyToClipboard } from '@will1123/lx-ui-utils'\r\n *\r\n * // 基础用法\r\n * const success = await copyToClipboard('Hello World')\r\n * if (success) {\r\n * console.log('复制成功')\r\n * } else {\r\n * console.log('复制失败')\r\n * }\r\n *\r\n * // 复制代码\r\n * const code = 'console.log(\"Hello World\")'\r\n * await copyToClipboard(code)\r\n *\r\n * // 复制 HTML 内容\r\n * const html = '<div>Hello</div>'\r\n * await copyToClipboard(html)\r\n * ```\r\n */\r\nexport async function copyToClipboard(text: string): Promise<boolean> {\r\n if (typeof window === 'undefined') return false;\r\n\r\n // 使用现代 Clipboard API\r\n if (navigator.clipboard && navigator.clipboard.writeText) {\r\n try {\r\n await navigator.clipboard.writeText(text);\r\n return true;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n return false;\r\n }\r\n }\r\n\r\n // 降级方案:使用 document.execCommand\r\n const textArea = document.createElement('textarea');\r\n textArea.value = text;\r\n textArea.style.position = 'fixed';\r\n textArea.style.opacity = '0';\r\n textArea.style.left = '-9999px';\r\n document.body.appendChild(textArea);\r\n textArea.focus();\r\n textArea.select();\r\n\r\n try {\r\n const successful = document.execCommand('copy');\r\n document.body.removeChild(textArea);\r\n return successful;\r\n } catch (error) {\r\n console.error('复制失败:', error);\r\n document.body.removeChild(textArea);\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * 判断值是否为 DOM 元素(HTMLElement 或 Document)\r\n *\r\n * @param val - 要判断的值\r\n * @returns 是否为 DOM 元素\r\n *\r\n * @example\r\n * ```typescript\r\n * import { isDom } from '@will1123/lx-ui-utils'\r\n *\r\n * isDom(document.body) // true\r\n * isDom(document) // true\r\n * isDom({}) // false\r\n * isDom('div') // false\r\n * ```\r\n */\r\nexport function isDom(val: unknown): val is HTMLElement | Document {\r\n return val instanceof HTMLElement || val instanceof Document;\r\n}\r\n"],"names":["uuidv4","fileSaveAs","XLSX"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BO,SAAS,mBAA2B;AACzC,SAAOA,QAAA;AACT;AAyBO,SAAS,OAAO,MAAmB,UAAyB;AACjE,QAAM,cACJ,aAAa,gBAAgB,OAAO,KAAK,OAAO;AAElDC,YAAAA,OAAW,MAAM,WAAW;AAC9B;AAyCO,SAAS,cACd,MACA,SAIM;AACN,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ;AAAA,EAAA,IACE,WAAW,CAAA;AAGf,QAAM,YAAYC,gBAAK,MAAM,cAAc,MAAM;AAAA,IAC/C,QAAQ,UAAU,OAAO,KAAK,OAAO,IAAI;AAAA,EAAA,CAC1C;AAGD,MAAI,WAAW,UAAU,MAAM,GAAG;AAChC,UAAM,QAAQA,gBAAK,MAAM,aAAa,UAAU,MAAM,CAAC;AACvD,UAAM,YAAY,MAAM,EAAE;AAE1B,WAAO,KAAK,OAAO,EAAE,QAAQ,CAAC,KAAK,UAAU;AAC3C,YAAM,cAAcA,gBAAK,MAAM,YAAY;AAAA,QACzC,GAAG;AAAA,QACH,GAAG;AAAA,MAAA,CACJ;AACD,gBAAU,WAAW,EAAE,IAAI,QAAQ,GAAG;AAAA,IACxC,CAAC;AAAA,EACH;AAGA,QAAM,WAAWA,gBAAK,MAAM,SAAA;AAC5BA,kBAAK,MAAM,kBAAkB,UAAU,WAAW,SAAS;AAG3D,QAAM,cAAcA,gBAAK,MAAM,UAAU;AAAA,IACvC,UAAU;AAAA,IACV,MAAM;AAAA,EAAA,CACP;AAED,SAAO,IAAI,KAAK,CAAC,WAAW,GAAG;AAAA,IAC7B,MAAM;AAAA,EAAA,CACP;AACH;AA6BA,SAAsB,gBAAgB,MAAgC;AAAA;AACpE,QAAI,OAAO,WAAW,YAAa,QAAO;AAG1C,QAAI,UAAU,aAAa,UAAU,UAAU,WAAW;AACxD,UAAI;AACF,cAAM,UAAU,UAAU,UAAU,IAAI;AACxC,eAAO;AAAA,MACT,SAAS,OAAO;AACd,gBAAQ,MAAM,SAAS,KAAK;AAC5B,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,WAAW,SAAS,cAAc,UAAU;AAClD,aAAS,QAAQ;AACjB,aAAS,MAAM,WAAW;AAC1B,aAAS,MAAM,UAAU;AACzB,aAAS,MAAM,OAAO;AACtB,aAAS,KAAK,YAAY,QAAQ;AAClC,aAAS,MAAA;AACT,aAAS,OAAA;AAET,QAAI;AACF,YAAM,aAAa,SAAS,YAAY,MAAM;AAC9C,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT,SAAS,OAAO;AACd,cAAQ,MAAM,SAAS,KAAK;AAC5B,eAAS,KAAK,YAAY,QAAQ;AAClC,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAkBO,SAAS,MAAM,KAA6C;AACjE,SAAO,eAAe,eAAe,eAAe;AACtD;;;;;;"}
package/dist/lib/index.js CHANGED
@@ -4,14 +4,19 @@ const sse = require("./sse.js");
4
4
  const thinking = require("./thinking.js");
5
5
  const markdown = require("./markdown.js");
6
6
  const helper = require("./helper.js");
7
+ const messageConfig = require("./message-config.js");
7
8
  exports.sse = sse.sse;
8
9
  exports.extractThinking = thinking.extractThinking;
9
10
  exports.bindMarkdownCodeBoxEvents = markdown.bindMarkdownCodeBoxEvents;
10
- exports.createMarkdownRender = markdown.createMarkdownRender;
11
+ exports.createMarkdownParser = markdown.createMarkdownParser;
11
12
  exports.getMarkdownCodeTheme = markdown.getMarkdownCodeTheme;
12
13
  exports.setMarkdownCodeTheme = markdown.setMarkdownCodeTheme;
13
14
  exports.toggleMarkdownCodeTheme = markdown.toggleMarkdownCodeTheme;
14
15
  exports.updateMarkdownCodeBlocksTheme = markdown.updateMarkdownCodeBlocksTheme;
15
16
  exports.copyToClipboard = helper.copyToClipboard;
17
+ exports.generateExcel = helper.generateExcel;
18
+ exports.generateUniqueId = helper.generateUniqueId;
16
19
  exports.isDom = helper.isDom;
20
+ exports.saveAs = helper.saveAs;
21
+ exports.getBaseMessageOption = messageConfig.getBaseMessageOption;
17
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;"}