deepfish-ai 1.0.29 → 2.0.2

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 (62) hide show
  1. package/README.md +126 -368
  2. package/dist/index.js +23320 -0
  3. package/dist/serve/client/assets/index-DUHYC91l.js +1 -0
  4. package/dist/serve/client/assets/index-zcrdNLZs.css +1 -0
  5. package/dist/serve/client/index.html +14 -0
  6. package/dist/serve/pm2-server.js +21015 -0
  7. package/dist/serve/server/entry-server.mjs +224 -0
  8. package/package.json +61 -83
  9. package/LICENSE +0 -21
  10. package/README_CN.md +0 -460
  11. package/src/AgentRobot/AgentRobotFactory/MainAgentRobot.js +0 -88
  12. package/src/AgentRobot/AgentRobotFactory/SubAgentRobot.js +0 -45
  13. package/src/AgentRobot/AgentRobotFactory/SubSkillAgentRobot.js +0 -45
  14. package/src/AgentRobot/AgentRobotFactory/index.js +0 -48
  15. package/src/AgentRobot/BaseAgentRobot/Brain.js +0 -210
  16. package/src/AgentRobot/BaseAgentRobot/BrainEvent.js +0 -17
  17. package/src/AgentRobot/BaseAgentRobot/Hand.js +0 -119
  18. package/src/AgentRobot/BaseAgentRobot/Heart.js +0 -0
  19. package/src/AgentRobot/BaseAgentRobot/Logger.js +0 -114
  20. package/src/AgentRobot/BaseAgentRobot/ScreenPrinter.js +0 -49
  21. package/src/AgentRobot/BaseAgentRobot/index.js +0 -266
  22. package/src/AgentRobot/BaseAgentRobot/lazy-tools/AliBailian.js +0 -997
  23. package/src/AgentRobot/BaseAgentRobot/lazy-tools/doc-transform.js +0 -204
  24. package/src/AgentRobot/BaseAgentRobot/lazy-tools/docx.js +0 -1286
  25. package/src/AgentRobot/BaseAgentRobot/lazy-tools/img.js +0 -765
  26. package/src/AgentRobot/BaseAgentRobot/lazy-tools/pdf.js +0 -736
  27. package/src/AgentRobot/BaseAgentRobot/lazy-tools/pptx.js +0 -567
  28. package/src/AgentRobot/BaseAgentRobot/lazy-tools/video.js +0 -36
  29. package/src/AgentRobot/BaseAgentRobot/lazy-tools/xlsx.js +0 -611
  30. package/src/AgentRobot/BaseAgentRobot/tools/BaseTools.js +0 -13
  31. package/src/AgentRobot/BaseAgentRobot/tools/CreateAgentTools.js +0 -158
  32. package/src/AgentRobot/BaseAgentRobot/tools/FileTools.js +0 -768
  33. package/src/AgentRobot/BaseAgentRobot/tools/GenerateTools.js +0 -441
  34. package/src/AgentRobot/BaseAgentRobot/tools/InquirerTools.js +0 -244
  35. package/src/AgentRobot/BaseAgentRobot/tools/SystemTools.js +0 -424
  36. package/src/AgentRobot/BaseAgentRobot/tools/TaskTools.js +0 -211
  37. package/src/AgentRobot/BaseAgentRobot/tools/TestTools.js +0 -100
  38. package/src/AgentRobot/BaseAgentRobot/tools/UserTool.js +0 -87
  39. package/src/AgentRobot/BaseAgentRobot/tools/WebTools.js +0 -257
  40. package/src/AgentRobot/BaseAgentRobot/utils/AIRequest.js +0 -297
  41. package/src/AgentRobot/BaseAgentRobot/utils/AIToolManager.js +0 -131
  42. package/src/AgentRobot/BaseAgentRobot/utils/AgentTree.js +0 -66
  43. package/src/AgentRobot/BaseAgentRobot/utils/AttachmentToolScanner.js +0 -235
  44. package/src/AgentRobot/BaseAgentRobot/utils/MessageCompresser.js +0 -236
  45. package/src/AgentRobot/BaseAgentRobot/utils/aiConsole.js +0 -74
  46. package/src/AgentRobot/BaseAgentRobot/utils/aiInquirer.js +0 -78
  47. package/src/AgentRobot/BaseAgentRobot/utils/analyzeReturn.test.js +0 -104
  48. package/src/AgentRobot/BaseAgentRobot/utils/copilot.js +0 -117
  49. package/src/AgentRobot/BaseAgentRobot/utils/node-root.js +0 -138
  50. package/src/AgentRobot/BaseAgentRobot/utils/normal.js +0 -399
  51. package/src/cli/ConfigManager.js +0 -283
  52. package/src/cli/DefaultConfig.js +0 -98
  53. package/src/cli/GlobalVariable.js +0 -8
  54. package/src/cli/MemoryManager.js +0 -53
  55. package/src/cli/SkillConfigManager.js +0 -354
  56. package/src/cli/SkillParser.js +0 -58
  57. package/src/cli/ai-auth.js +0 -231
  58. package/src/cli/ai-config.js +0 -225
  59. package/src/cli/ai-memory.js +0 -22
  60. package/src/cli/ai-skill.js +0 -65
  61. package/src/cli/index.js +0 -77
  62. package/src/index.js +0 -54
@@ -1,1286 +0,0 @@
1
- /**
2
- * @Author: Roman 306863030@qq.com
3
- * @Description: Word 文档处理工具集(mammoth / docx / pizzip / docxtemplater)
4
- */
5
- const path = require('path')
6
- const fs = require('fs-extra')
7
- const mammoth = require('mammoth')
8
- const docx = require('docx')
9
- const PizZip = require('pizzip')
10
- const Docxtemplater = require('docxtemplater')
11
- const cheerio = require('cheerio')
12
-
13
- // ─── 统一返回结构 ─────────────────────────────────────────────────────────────
14
-
15
- function ok(data = null) {
16
- return { success: true, data }
17
- }
18
-
19
- function fail(error, data = null) {
20
- return { success: false, error: error?.message || String(error), data }
21
- }
22
-
23
- function resolvePath(filePath) {
24
- return path.resolve(process.cwd(), filePath)
25
- }
26
-
27
- // ─── 格式转换辅助函数 ────────────────────────────────────────────────────────
28
-
29
- /**
30
- * 使用 puppeteer 将 HTML 字符串渲染为 PDF 文件
31
- */
32
- async function htmlStringToPdf(html, outputPath) {
33
- let puppeteer
34
- try {
35
- puppeteer = require('puppeteer')
36
- } catch {
37
- throw new Error('puppeteer 未安装,请先执行 npm install puppeteer')
38
- }
39
- const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] })
40
- try {
41
- const page = await browser.newPage()
42
- await page.setContent(html, { waitUntil: 'networkidle0' })
43
- fs.ensureDirSync(path.dirname(outputPath))
44
- await page.pdf({ path: outputPath, format: 'A4', printBackground: true })
45
- } finally {
46
- await browser.close()
47
- }
48
- }
49
-
50
- function escapeHtml(str) {
51
- return str
52
- .replace(/&/g, '&')
53
- .replace(/</g, '&lt;')
54
- .replace(/>/g, '&gt;')
55
- }
56
-
57
- function unescapeXml(str) {
58
- return str
59
- .replace(/&amp;/g, '&')
60
- .replace(/&lt;/g, '<')
61
- .replace(/&gt;/g, '>')
62
- .replace(/&quot;/g, '"')
63
- .replace(/&apos;/g, "'")
64
- }
65
-
66
- function buildRunXml(rPr, text) {
67
- if (!text) return ''
68
- const xmlSp = /^\s|\s$/.test(text) ? ' xml:space="preserve"' : ''
69
- return `<w:r>${rPr}<w:t${xmlSp}>${escapeHtml(text)}</w:t></w:r>`
70
- }
71
-
72
- /**
73
- * 将 Markdown 文本转换为 HTML 字符串
74
- */
75
- function markdownToHtmlString(md) {
76
- let html = md
77
- .replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>
78
- `<pre><code class="language-${lang}">${escapeHtml(code.trimEnd())}</code></pre>`)
79
- .replace(/`([^`]+)`/g, (_, c) => `<code>${escapeHtml(c)}</code>`)
80
- .replace(/^###### (.+)$/gm, '<h6>$1</h6>')
81
- .replace(/^##### (.+)$/gm, '<h5>$1</h5>')
82
- .replace(/^#### (.+)$/gm, '<h4>$1</h4>')
83
- .replace(/^### (.+)$/gm, '<h3>$1</h3>')
84
- .replace(/^## (.+)$/gm, '<h2>$1</h2>')
85
- .replace(/^# (.+)$/gm, '<h1>$1</h1>')
86
- .replace(/^[-*_]{3,}$/gm, '<hr>')
87
- .replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
88
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
89
- .replace(/__(.+?)__/g, '<strong>$1</strong>')
90
- .replace(/\*(.+?)\*/g, '<em>$1</em>')
91
- .replace(/_(.+?)_/g, '<em>$1</em>')
92
- .replace(/~~(.+?)~~/g, '<del>$1</del>')
93
- .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img alt="$1" src="$2">')
94
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
95
- .replace(/^[ \t]*[-*+] (.+)$/gm, '<li>$1</li>')
96
- .replace(/^[ \t]*\d+\. (.+)$/gm, '<li>$1</li>')
97
- .replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
98
- html = html.replace(/(<li>[\s\S]+?<\/li>)(\n(?!<li>)|$)/g, (_, items) => `<ul>${items}</ul>`)
99
- html = html.replace(/^(?!<[a-z]|$)(.+)$/gm, '<p>$1</p>')
100
- return `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
101
- body{font-family:sans-serif;line-height:1.7;max-width:900px;margin:40px auto;padding:0 20px;color:#333}
102
- h1,h2,h3,h4,h5,h6{margin-top:1.2em}
103
- pre{background:#f5f5f5;padding:12px;border-radius:4px;overflow:auto}
104
- code{background:#f0f0f0;padding:2px 4px;border-radius:3px}
105
- blockquote{border-left:4px solid #ddd;margin:0;padding-left:1em;color:#666}
106
- table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:6px 10px}
107
- </style></head><body>${html}</body></html>`
108
- }
109
-
110
- /**
111
- * 将 HTML 字符串转换为 Markdown 文本
112
- */
113
- function htmlStringToMarkdown(html) {
114
- const $ = cheerio.load(html)
115
- const body = $('body').length ? $('body') : $.root()
116
-
117
- function nodeToMd(el) {
118
- const node = $(el)
119
- const tag = el.type === 'text' ? '#text' : (el.name || '').toLowerCase()
120
- if (el.type === 'text') return el.data || ''
121
- const inner = () => node.contents().toArray().map(nodeToMd).join('')
122
- switch (tag) {
123
- case 'h1': return `# ${inner()}\n\n`
124
- case 'h2': return `## ${inner()}\n\n`
125
- case 'h3': return `### ${inner()}\n\n`
126
- case 'h4': return `#### ${inner()}\n\n`
127
- case 'h5': return `##### ${inner()}\n\n`
128
- case 'h6': return `###### ${inner()}\n\n`
129
- case 'p': return `${inner()}\n\n`
130
- case 'br': return '\n'
131
- case 'hr': return '---\n\n'
132
- case 'strong':
133
- case 'b': return `**${inner()}**`
134
- case 'em':
135
- case 'i': return `*${inner()}*`
136
- case 'del':
137
- case 's': return `~~${inner()}~~`
138
- case 'code': return `\`${inner()}\``
139
- case 'pre': {
140
- const codeEl = node.find('code')
141
- const lang = (codeEl.attr('class') || '').replace('language-', '')
142
- const content = codeEl.length ? codeEl.text() : node.text()
143
- return `\`\`\`${lang}\n${content}\n\`\`\`\n\n`
144
- }
145
- case 'blockquote': return inner().split('\n').map(l => l ? `> ${l}` : '').join('\n') + '\n\n'
146
- case 'a': return `[${inner()}](${node.attr('href') || ''})`
147
- case 'img': return `![${node.attr('alt') || ''}](${node.attr('src') || ''})`
148
- case 'ul':
149
- case 'ol': return inner() + '\n'
150
- case 'li': return `- ${inner()}\n`
151
- case 'table': {
152
- const rows = node.find('tr').toArray()
153
- if (!rows.length) return ''
154
- return rows.map((row, i) => {
155
- const cells = $(row).find('th,td').toArray().map(c => $(c).text().trim())
156
- const line = `| ${cells.join(' | ')} |`
157
- return i === 0 ? `${line}\n| ${cells.map(() => '---').join(' | ')} |` : line
158
- }).join('\n') + '\n\n'
159
- }
160
- case 'head':
161
- case 'style':
162
- case 'script': return ''
163
- default: return inner()
164
- }
165
- }
166
-
167
- return body.contents().toArray().map(nodeToMd).join('').replace(/\n{3,}/g, '\n\n').trim()
168
- }
169
-
170
- /**
171
- * 将 HTML 字符串解析为 docx sections 数组
172
- */
173
- function htmlStringToDocxSections(html) {
174
- const $ = cheerio.load(html)
175
- const sections = []
176
-
177
- function processNode(el) {
178
- const node = $(el)
179
- const tag = (el.name || '').toLowerCase()
180
- const headingMatch = tag.match(/^h([1-6])$/)
181
- if (headingMatch) {
182
- sections.push({ type: 'heading', level: parseInt(headingMatch[1]), text: node.text().trim() })
183
- return
184
- }
185
- switch (tag) {
186
- case 'p':
187
- if (node.text().trim()) sections.push({ type: 'paragraph', text: node.text().trim() })
188
- break
189
- case 'ul':
190
- sections.push({ type: 'list', items: node.find('li').toArray().map(li => $(li).text().trim()) })
191
- break
192
- case 'ol':
193
- sections.push({ type: 'numberedList', items: node.find('li').toArray().map(li => $(li).text().trim()) })
194
- break
195
- case 'table': {
196
- const rows = node.find('tr').toArray().map(row =>
197
- $(row).find('th,td').toArray().map(c => $(c).text().trim()))
198
- if (rows.length) sections.push({ type: 'table', rows })
199
- break
200
- }
201
- case 'hr':
202
- sections.push({ type: 'horizontalRule' })
203
- break
204
- case 'pre':
205
- sections.push({ type: 'paragraph', text: node.text() })
206
- break
207
- case 'blockquote':
208
- node.find('p').each((_, pEl) => {
209
- const t = $(pEl).text().trim()
210
- if (t) sections.push({ type: 'paragraph', text: `> ${t}` })
211
- })
212
- if (!node.find('p').length && node.text().trim()) {
213
- sections.push({ type: 'paragraph', text: `> ${node.text().trim()}` })
214
- }
215
- break
216
- default:
217
- node.children().each((_, child) => processNode(child))
218
- }
219
- }
220
-
221
- $('body').children().each((_, el) => processNode(el))
222
- return sections
223
- }
224
-
225
- // ─── 内部辅助:将 sections 描述转换为 docx children ──────────────────────────
226
- /**
227
- * sections 数组每项结构:
228
- * { type: 'paragraph'|'heading'|'table'|'list'|'numberedList'|'pageBreak'|'horizontalRule',
229
- * text, level, rows, items, runs, bold, italic, alignment, color, fontSize }
230
- * runs 数组每项:{ text, bold, italic, underline, color, fontSize }
231
- */
232
- function buildChildren(sections, docxLib) {
233
- const {
234
- Paragraph, TextRun, HeadingLevel,
235
- Table, TableRow, TableCell, WidthType,
236
- AlignmentType, ExternalHyperlink,
237
- } = docxLib
238
-
239
- const HEADING_MAP = {
240
- 1: HeadingLevel.HEADING_1,
241
- 2: HeadingLevel.HEADING_2,
242
- 3: HeadingLevel.HEADING_3,
243
- 4: HeadingLevel.HEADING_4,
244
- 5: HeadingLevel.HEADING_5,
245
- 6: HeadingLevel.HEADING_6,
246
- }
247
-
248
- function makeRun(r) {
249
- return new TextRun({
250
- text: String(r.text ?? ''),
251
- bold: r.bold || false,
252
- italics: r.italic || false,
253
- underline: r.underline ? {} : undefined,
254
- color: r.color || undefined,
255
- size: r.fontSize ? r.fontSize * 2 : undefined,
256
- })
257
- }
258
-
259
- const alignment = (a) =>
260
- a ? AlignmentType[a.toUpperCase()] || AlignmentType.LEFT : undefined
261
-
262
- const children = []
263
- for (const sec of (sections || [])) {
264
- switch (sec.type) {
265
- case 'heading':
266
- children.push(
267
- new Paragraph({
268
- text: String(sec.text || ''),
269
- heading: HEADING_MAP[sec.level || 1] || HeadingLevel.HEADING_1,
270
- alignment: alignment(sec.alignment),
271
- }),
272
- )
273
- break
274
-
275
- case 'paragraph': {
276
- const runs = Array.isArray(sec.runs)
277
- ? sec.runs.map(makeRun)
278
- : [new TextRun({
279
- text: String(sec.text || ''),
280
- bold: sec.bold || false,
281
- italics: sec.italic || false,
282
- color: sec.color || undefined,
283
- size: sec.fontSize ? sec.fontSize * 2 : undefined,
284
- })]
285
- children.push(new Paragraph({ children: runs, alignment: alignment(sec.alignment) }))
286
- break
287
- }
288
-
289
- case 'table': {
290
- const colCount = Math.max(...(sec.rows || [[]]).map(r => r.length), 1)
291
- const colWidth = Math.floor(9000 / colCount)
292
- const rows = (sec.rows || []).map((row) =>
293
- new TableRow({
294
- children: (row || []).map((cell) =>
295
- new TableCell({
296
- children: [new Paragraph({ text: String(cell ?? '') })],
297
- width: { size: colWidth, type: WidthType.DXA },
298
- }),
299
- ),
300
- }),
301
- )
302
- children.push(new Table({ rows, width: { size: 9000, type: WidthType.DXA } }))
303
- break
304
- }
305
-
306
- case 'list':
307
- for (const item of (sec.items || [])) {
308
- children.push(new Paragraph({ text: String(item), bullet: { level: sec.level || 0 } }))
309
- }
310
- break
311
-
312
- case 'numberedList': {
313
- const numRef = sec.numReference || 'default-numbering'
314
- for (const item of (sec.items || [])) {
315
- children.push(
316
- new Paragraph({
317
- text: String(item),
318
- numbering: { reference: numRef, level: 0 },
319
- }),
320
- )
321
- }
322
- break
323
- }
324
-
325
- case 'pageBreak':
326
- children.push(new Paragraph({ pageBreakBefore: true }))
327
- break
328
-
329
- case 'horizontalRule':
330
- children.push(
331
- new Paragraph({
332
- children: [new TextRun({ text: '', break: 1 })],
333
- border: { bottom: { color: '999999', space: 1, value: 'single', size: 6 } },
334
- }),
335
- )
336
- break
337
-
338
- default:
339
- // 未知类型作为普通段落处理
340
- if (sec.text) {
341
- children.push(new Paragraph({ text: String(sec.text) }))
342
- }
343
- }
344
- }
345
- return children
346
- }
347
-
348
- // ─── 工具函数 ─────────────────────────────────────────────────────────────────
349
-
350
- /**
351
- * 读取 Word 文档为纯文本
352
- */
353
- async function readDocxText(filePath) {
354
- try {
355
- const fullPath = resolvePath(filePath)
356
- if (!fs.existsSync(fullPath)) {
357
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
358
- }
359
- const result = await mammoth.extractRawText({ path: fullPath })
360
- return ok({ filePath: fullPath, text: result.value, messages: result.messages })
361
- } catch (error) {
362
- return fail(error, { filePath })
363
- }
364
- }
365
-
366
- /**
367
- * 读取 Word 文档为 HTML
368
- */
369
- async function readDocxHtml(filePath) {
370
- try {
371
- const fullPath = resolvePath(filePath)
372
- if (!fs.existsSync(fullPath)) {
373
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
374
- }
375
- const result = await mammoth.convertToHtml({ path: fullPath })
376
- return ok({ filePath: fullPath, html: result.value, messages: result.messages })
377
- } catch (error) {
378
- return fail(error, { filePath })
379
- }
380
- }
381
-
382
- /**
383
- * 获取 Word 文档基础信息(字数、行数、文件大小等)
384
- */
385
- async function getDocxInfo(filePath) {
386
- try {
387
- const fullPath = resolvePath(filePath)
388
- if (!fs.existsSync(fullPath)) {
389
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
390
- }
391
- const result = await mammoth.extractRawText({ path: fullPath })
392
- const text = result.value || ''
393
- const stats = fs.statSync(fullPath)
394
- return ok({
395
- filePath: fullPath,
396
- size: stats.size,
397
- mtime: stats.mtime,
398
- wordCount: text.trim().split(/\s+/).filter(Boolean).length,
399
- charCount: text.length,
400
- lineCount: text.split('\n').length,
401
- paragraphCount: text.split(/\n\n+/).filter(Boolean).length,
402
- })
403
- } catch (error) {
404
- return fail(error, { filePath })
405
- }
406
- }
407
-
408
- /**
409
- * 搜索 Word 文档中是否包含指定文本,返回匹配行列表
410
- */
411
- async function searchDocxText(filePath, keyword) {
412
- try {
413
- const readResult = await readDocxText(filePath)
414
- if (!readResult.success) return readResult
415
- const lines = readResult.data.text.split('\n')
416
- const matches = lines
417
- .map((line, idx) => ({ line: idx + 1, text: line }))
418
- .filter(({ text }) => text.includes(keyword))
419
- return ok({
420
- filePath: resolvePath(filePath),
421
- keyword,
422
- matchCount: matches.length,
423
- matches,
424
- })
425
- } catch (error) {
426
- return fail(error, { filePath, keyword })
427
- }
428
- }
429
-
430
- /**
431
- * 将 Word 文档导出为纯文本文件
432
- */
433
- async function docxToText(inputPath, outputPath) {
434
- try {
435
- const readResult = await readDocxText(inputPath)
436
- if (!readResult.success) return readResult
437
- const fullOutputPath = resolvePath(outputPath)
438
- fs.ensureDirSync(path.dirname(fullOutputPath))
439
- fs.writeFileSync(fullOutputPath, readResult.data.text, 'utf8')
440
- return ok({ inputPath: resolvePath(inputPath), outputPath: fullOutputPath })
441
- } catch (error) {
442
- return fail(error, { inputPath, outputPath })
443
- }
444
- }
445
-
446
- /**
447
- * 将 Word 文档导出为 HTML 文件
448
- */
449
- async function docxToHtml(inputPath, outputPath) {
450
- try {
451
- const readResult = await readDocxHtml(inputPath)
452
- if (!readResult.success) return readResult
453
- const html = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body>${readResult.data.html}</body></html>`
454
- const fullOutputPath = resolvePath(outputPath)
455
- fs.ensureDirSync(path.dirname(fullOutputPath))
456
- fs.writeFileSync(fullOutputPath, html, 'utf8')
457
- return ok({ inputPath: resolvePath(inputPath), outputPath: fullOutputPath })
458
- } catch (error) {
459
- return fail(error, { inputPath, outputPath })
460
- }
461
- }
462
-
463
- /**
464
- * 创建 Word 文档(支持段落、标题、表格、列表等)
465
- */
466
- async function createDocx(filePath, sections = []) {
467
- try {
468
- const { Document, Packer } = docx
469
- const fullPath = resolvePath(filePath)
470
- fs.ensureDirSync(path.dirname(fullPath))
471
- const children = buildChildren(sections, docx)
472
- const doc = new Document({ sections: [{ properties: {}, children }] })
473
- const buffer = await Packer.toBuffer(doc)
474
- fs.writeFileSync(fullPath, buffer)
475
- return ok({ filePath: fullPath, sectionCount: sections.length })
476
- } catch (error) {
477
- return fail(error, { filePath })
478
- }
479
- }
480
-
481
- /**
482
- * 向已有 Word 文档末尾追加内容(以原文本段落 + 新 sections 重建文档)
483
- */
484
- async function appendToDocx(filePath, sections = []) {
485
- try {
486
- const readResult = await readDocxText(filePath)
487
- if (!readResult.success) return readResult
488
- const existingSections = readResult.data.text
489
- .split('\n')
490
- .filter(Boolean)
491
- .map((line) => ({ type: 'paragraph', text: line }))
492
- return await createDocx(filePath, [...existingSections, ...sections])
493
- } catch (error) {
494
- return fail(error, { filePath })
495
- }
496
- }
497
-
498
- /**
499
- * 替换 Word 文档中的文本内容(通过直接修改 XML 实现)
500
- */
501
- async function replaceDocxText(filePath, searchText, replaceText) {
502
- try {
503
- const fullPath = resolvePath(filePath)
504
- if (!fs.existsSync(fullPath)) {
505
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
506
- }
507
- const content = fs.readFileSync(fullPath, 'binary')
508
- const zip = new PizZip(content)
509
- const targets = ['word/document.xml', 'word/header1.xml', 'word/footer1.xml']
510
- let replaceCount = 0
511
- for (const target of targets) {
512
- const file = zip.file(target)
513
- if (!file) continue
514
- let xml = file.asText()
515
- const before = xml.split(searchText).length - 1
516
- xml = xml.split(searchText).join(replaceText)
517
- replaceCount += before
518
- zip.file(target, xml)
519
- }
520
- const buf = zip.generate({ type: 'nodebuffer', compression: 'DEFLATE' })
521
- fs.writeFileSync(fullPath, buf)
522
- return ok({ filePath: fullPath, searchText, replaceText, replaceCount })
523
- } catch (error) {
524
- return fail(error, { filePath, searchText, replaceText })
525
- }
526
- }
527
-
528
- /**
529
- * 用数据填充 Word 模板(模板标签格式:{变量名})
530
- */
531
- async function fillDocxTemplate(templatePath, outputPath, data) {
532
- try {
533
- const fullTemplatePath = resolvePath(templatePath)
534
- const fullOutputPath = resolvePath(outputPath)
535
- if (!fs.existsSync(fullTemplatePath)) {
536
- return fail(`Template file does not exist: ${fullTemplatePath}`, { templatePath: fullTemplatePath })
537
- }
538
- const content = fs.readFileSync(fullTemplatePath, 'binary')
539
- const zip = new PizZip(content)
540
- const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true })
541
- doc.render(data || {})
542
- const buf = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE' })
543
- fs.ensureDirSync(path.dirname(fullOutputPath))
544
- fs.writeFileSync(fullOutputPath, buf)
545
- return ok({ templatePath: fullTemplatePath, outputPath: fullOutputPath })
546
- } catch (error) {
547
- return fail(error, { templatePath, outputPath })
548
- }
549
- }
550
-
551
- /**
552
- * 合并多个 Word 文档为一个(以分页符分隔各文档内容)
553
- */
554
- async function mergeDocx(inputPaths, outputPath) {
555
- try {
556
- const allSections = []
557
- for (let i = 0; i < inputPaths.length; i++) {
558
- const readResult = await readDocxText(inputPaths[i])
559
- if (!readResult.success) return readResult
560
- const paras = readResult.data.text
561
- .split('\n')
562
- .filter(Boolean)
563
- .map((line) => ({ type: 'paragraph', text: line }))
564
- allSections.push(...paras)
565
- if (i < inputPaths.length - 1) {
566
- allSections.push({ type: 'pageBreak' })
567
- }
568
- }
569
- return await createDocx(outputPath, allSections)
570
- } catch (error) {
571
- return fail(error, { outputPath })
572
- }
573
- }
574
-
575
- /**
576
- * 提取 Word 文档中的所有超链接
577
- */
578
- async function extractDocxLinks(filePath) {
579
- try {
580
- const fullPath = resolvePath(filePath)
581
- if (!fs.existsSync(fullPath)) {
582
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
583
- }
584
- const content = fs.readFileSync(fullPath, 'binary')
585
- const zip = new PizZip(content)
586
- const relsFile = zip.file('word/_rels/document.xml.rels')
587
- if (!relsFile) return ok({ filePath: fullPath, links: [] })
588
- const relsXml = relsFile.asText()
589
- const linkRegex = /Type="http:\/\/schemas\.openxmlformats\.org\/officeDocument\/2006\/relationships\/hyperlink"\s+Target="([^"]+)"/g
590
- const links = []
591
- let m
592
- while ((m = linkRegex.exec(relsXml)) !== null) {
593
- links.push(m[1])
594
- }
595
- return ok({ filePath: fullPath, links })
596
- } catch (error) {
597
- return fail(error, { filePath })
598
- }
599
- }
600
-
601
- /**
602
- * 统计 Word 文档中各段落字数
603
- */
604
- async function getDocxParagraphStats(filePath) {
605
- try {
606
- const readResult = await readDocxText(filePath)
607
- if (!readResult.success) return readResult
608
- const paragraphs = readResult.data.text.split('\n').filter(Boolean)
609
- const stats = paragraphs.map((para, idx) => ({
610
- index: idx + 1,
611
- text: para.substring(0, 60) + (para.length > 60 ? '...' : ''),
612
- wordCount: para.trim().split(/\s+/).filter(Boolean).length,
613
- charCount: para.length,
614
- }))
615
- return ok({
616
- filePath: resolvePath(filePath),
617
- paragraphCount: paragraphs.length,
618
- totalWordCount: stats.reduce((s, p) => s + p.wordCount, 0),
619
- paragraphs: stats,
620
- })
621
- } catch (error) {
622
- return fail(error, { filePath })
623
- }
624
- }
625
-
626
- /**
627
- * 将 Word 文档的全部内容替换为新的 sections 内容
628
- */
629
- async function overwriteDocx(filePath, sections = []) {
630
- try {
631
- const fullPath = resolvePath(filePath)
632
- if (!fs.existsSync(fullPath)) {
633
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
634
- }
635
- return await createDocx(filePath, sections)
636
- } catch (error) {
637
- return fail(error, { filePath })
638
- }
639
- }
640
-
641
- // ─── Word 格式转换函数 ───────────────────────────────────────────────────────
642
-
643
- /**
644
- * Word 转 PDF
645
- */
646
- async function wordToPdf(inputPath, outputPath) {
647
- try {
648
- const fullInput = resolvePath(inputPath)
649
- const fullOutput = resolvePath(outputPath)
650
- if (!fs.existsSync(fullInput)) {
651
- return fail(`File does not exist: ${fullInput}`, { inputPath: fullInput })
652
- }
653
- const result = await mammoth.convertToHtml({ path: fullInput })
654
- const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
655
- body{font-family:sans-serif;line-height:1.7;margin:40px;color:#333}
656
- table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:6px 10px}
657
- </style></head><body>${result.value}</body></html>`
658
- await htmlStringToPdf(html, fullOutput)
659
- return ok({ inputPath: fullInput, outputPath: fullOutput })
660
- } catch (error) {
661
- return fail(error, { inputPath, outputPath })
662
- }
663
- }
664
-
665
- /**
666
- * Word 转 HTML
667
- */
668
- async function wordToHtml(inputPath, outputPath) {
669
- try {
670
- const fullInput = resolvePath(inputPath)
671
- const fullOutput = resolvePath(outputPath)
672
- if (!fs.existsSync(fullInput)) {
673
- return fail(`File does not exist: ${fullInput}`, { inputPath: fullInput })
674
- }
675
- const result = await mammoth.convertToHtml({ path: fullInput })
676
- const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><style>
677
- body{font-family:sans-serif;line-height:1.7;max-width:900px;margin:40px auto;padding:0 20px;color:#333}
678
- table{border-collapse:collapse;width:100%}td,th{border:1px solid #ddd;padding:6px 10px}
679
- </style></head><body>${result.value}</body></html>`
680
- fs.ensureDirSync(path.dirname(fullOutput))
681
- fs.writeFileSync(fullOutput, html, 'utf8')
682
- return ok({ inputPath: fullInput, outputPath: fullOutput, messages: result.messages })
683
- } catch (error) {
684
- return fail(error, { inputPath, outputPath })
685
- }
686
- }
687
-
688
- /**
689
- * Word 转 Markdown
690
- */
691
- async function wordToMarkdown(inputPath, outputPath) {
692
- try {
693
- const fullInput = resolvePath(inputPath)
694
- const fullOutput = resolvePath(outputPath)
695
- if (!fs.existsSync(fullInput)) {
696
- return fail(`File does not exist: ${fullInput}`, { inputPath: fullInput })
697
- }
698
- const result = await mammoth.convertToHtml({ path: fullInput })
699
- const md = htmlStringToMarkdown(result.value)
700
- fs.ensureDirSync(path.dirname(fullOutput))
701
- fs.writeFileSync(fullOutput, md, 'utf8')
702
- return ok({ inputPath: fullInput, outputPath: fullOutput })
703
- } catch (error) {
704
- return fail(error, { inputPath, outputPath })
705
- }
706
- }
707
-
708
- /**
709
- * Markdown 转 Word
710
- */
711
- async function markdownToWord(inputPath, outputPath) {
712
- try {
713
- const fullInput = resolvePath(inputPath)
714
- const fullOutput = resolvePath(outputPath)
715
- if (!fs.existsSync(fullInput)) {
716
- return fail(`File does not exist: ${fullInput}`, { inputPath: fullInput })
717
- }
718
- const md = fs.readFileSync(fullInput, 'utf8')
719
- const html = markdownToHtmlString(md)
720
- const sections = htmlStringToDocxSections(html)
721
- const { Document, Packer } = docx
722
- fs.ensureDirSync(path.dirname(fullOutput))
723
- const children = buildChildren(sections, docx)
724
- const doc = new Document({ sections: [{ properties: {}, children }] })
725
- const buffer = await Packer.toBuffer(doc)
726
- fs.writeFileSync(fullOutput, buffer)
727
- return ok({ inputPath: fullInput, outputPath: fullOutput, sectionCount: sections.length })
728
- } catch (error) {
729
- return fail(error, { inputPath, outputPath })
730
- }
731
- }
732
-
733
- /**
734
- * HTML 转 Word
735
- */
736
- async function htmlToWord(inputPath, outputPath) {
737
- try {
738
- const fullInput = resolvePath(inputPath)
739
- const fullOutput = resolvePath(outputPath)
740
- if (!fs.existsSync(fullInput)) {
741
- return fail(`File does not exist: ${fullInput}`, { inputPath: fullInput })
742
- }
743
- const html = fs.readFileSync(fullInput, 'utf8')
744
- const sections = htmlStringToDocxSections(html)
745
- const { Document, Packer } = docx
746
- fs.ensureDirSync(path.dirname(fullOutput))
747
- const children = buildChildren(sections, docx)
748
- const doc = new Document({ sections: [{ properties: {}, children }] })
749
- const buffer = await Packer.toBuffer(doc)
750
- fs.writeFileSync(fullOutput, buffer)
751
- return ok({ inputPath: fullInput, outputPath: fullOutput, sectionCount: sections.length })
752
- } catch (error) {
753
- return fail(error, { inputPath, outputPath })
754
- }
755
- }
756
-
757
- // ─── 工具描述 ─────────────────────────────────────────────────────────────────
758
-
759
- /**
760
- * 保留原格式修改 Word 文档内容(支持跨 <w:r> run 的文本替换,不破坏其余样式)
761
- * @param {string} filePath - .docx 文件路径
762
- * @param {Array<{search:string, replace:string}>} replacements - 替换规则数组
763
- */
764
- async function patchDocxText(filePath, replacements) {
765
- try {
766
- const fullPath = resolvePath(filePath)
767
- if (!fs.existsSync(fullPath)) {
768
- return fail(`File does not exist: ${fullPath}`, { filePath: fullPath })
769
- }
770
- const content = fs.readFileSync(fullPath, 'binary')
771
- const zip = new PizZip(content)
772
- const docFile = zip.file('word/document.xml')
773
- if (!docFile) return fail('word/document.xml not found', { filePath: fullPath })
774
- let xml = docFile.asText()
775
- let totalCount = 0
776
-
777
- for (const { search, replace: replaceStr } of (replacements || [])) {
778
- if (!search) continue
779
-
780
- xml = xml.replace(/<w:p(?:\s[^>]*)?>([\s\S]*?)<\/w:p>/g, (para) => {
781
- // 提取所有含文本的 <w:r> 元素
782
- const runRe = /<w:r(?:\s[^>]*)?>([\s\S]*?)<\/w:r>/g
783
- const runs = []
784
- let rm
785
- while ((rm = runRe.exec(para)) !== null) {
786
- const tM = rm[0].match(/<w:t(?:\s[^>]*)?>([^<]*)<\/w:t>/)
787
- if (!tM) continue // 跳过无文本内容的 run(如字段符号)
788
- const rPrM = rm[0].match(/<w:rPr>[\s\S]*?<\/w:rPr>/)
789
- runs.push({
790
- full: rm[0],
791
- rPr: rPrM ? rPrM[0] : '',
792
- text: unescapeXml(tM[1]),
793
- })
794
- }
795
- if (!runs.length) return para
796
-
797
- const fullText = runs.map(r => r.text).join('')
798
- if (!fullText.includes(search)) return para
799
-
800
- // 构建每个 run 的字符偏移区间
801
- let off = 0
802
- const offsets = runs.map(r => { const s = off; off += r.text.length; return { start: s, end: off } })
803
-
804
- const findRunIdx = (pos) => {
805
- const idx = offsets.findIndex(o => pos >= o.start && pos < o.end)
806
- return idx >= 0 ? idx : runs.length - 1
807
- }
808
-
809
- // 找所有非重叠匹配位置
810
- const occurrences = []
811
- let from = 0
812
- while (true) {
813
- const pos = fullText.indexOf(search, from)
814
- if (pos === -1) break
815
- occurrences.push(pos)
816
- from = pos + search.length
817
- }
818
- totalCount += occurrences.length
819
-
820
- // 按字符位置构建新的 {rPr, text} 片段列表
821
- const segs = []
822
- let cursor = 0
823
- for (const matchPos of occurrences) {
824
- // 匹配前的文本:按 run 边界拆分,保留各自 rPr
825
- let p = cursor
826
- while (p < matchPos) {
827
- const ri = findRunIdx(p)
828
- const runEnd = Math.min(offsets[ri].end, matchPos)
829
- segs.push({ rPr: runs[ri].rPr, text: fullText.slice(p, runEnd) })
830
- p = runEnd
831
- }
832
- // 替换文本:使用匹配起始位置所在 run 的 rPr
833
- segs.push({ rPr: runs[findRunIdx(matchPos)].rPr, text: replaceStr })
834
- cursor = matchPos + search.length
835
- }
836
- // 匹配后剩余文本
837
- let p = cursor
838
- while (p < fullText.length) {
839
- const ri = findRunIdx(p)
840
- segs.push({ rPr: runs[ri].rPr, text: fullText.slice(p, offsets[ri].end) })
841
- p = offsets[ri].end
842
- }
843
-
844
- // 合并相邻且 rPr 相同的片段
845
- const merged = []
846
- for (const seg of segs) {
847
- if (!seg.text) continue
848
- if (merged.length && merged[merged.length - 1].rPr === seg.rPr) {
849
- merged[merged.length - 1].text += seg.text
850
- } else {
851
- merged.push({ rPr: seg.rPr, text: seg.text })
852
- }
853
- }
854
-
855
- // 构建新的 runs XML
856
- const newRunsXml = merged.map(s => buildRunXml(s.rPr, s.text)).join('')
857
-
858
- // 将段落中从第一个到最后一个文本 run 的区间替换为新 runs
859
- const firstRunFull = runs[0].full
860
- const lastRunFull = runs[runs.length - 1].full
861
- const firstIdx = para.indexOf(firstRunFull)
862
- const lastIdx = para.lastIndexOf(lastRunFull)
863
- if (firstIdx === -1 || lastIdx === -1) return para
864
- return para.slice(0, firstIdx) + newRunsXml + para.slice(lastIdx + lastRunFull.length)
865
- })
866
- }
867
-
868
- zip.file('word/document.xml', xml)
869
- const buf = zip.generate({ type: 'nodebuffer', compression: 'DEFLATE' })
870
- fs.writeFileSync(fullPath, buf)
871
- return ok({ filePath: fullPath, totalReplacements: totalCount })
872
- } catch (error) {
873
- return fail(error, { filePath })
874
- }
875
- }
876
-
877
- // ─── 工具描述 ─────────────────────────────────────────────────────────────────
878
-
879
-
880
- // ─── 使用说明 ─────────────────────────────────────────────────────────────────
881
-
882
- function docxReadme() {
883
- return `【DOCX 工具使用说明】
884
- 1. 优先使用本工具内置函数完成任务(读取、创建、搜索、替换、模板填充、格式转换、合并等)。
885
- 2. 如果内置函数无法满足需求(如复杂排版保真、特殊宏处理、跨格式高级转换),再尝试使用 LibreOffice 命令行。
886
- 3. 在调用 LibreOffice 前,先检测系统是否已安装 LibreOffice:
887
- - 已安装:直接使用 LibreOffice 命令行继续处理。
888
- - 未安装:询问用户是否允许安装。
889
- 4. 若用户同意安装:引导完成安装后继续执行原任务。
890
- 5. 若用户拒绝安装:明确告知当前能力限制,并终止该操作。
891
-
892
- 建议:
893
- - 常规文档处理优先使用内置函数,速度更快且依赖更少。
894
- - 仅在确实需要高保真格式转换或复杂排版时才启用 LibreOffice 路径。`
895
- }
896
-
897
- const descriptions = [
898
- {
899
- type: 'function',
900
- function: {
901
- name: 'docxReadme',
902
- description: '获取 DOCX 工具集的使用说明, 调用函数前必须先查看本说明。',
903
- parameters: {},
904
- }
905
- },
906
- {
907
- type: 'function',
908
- function: {
909
- name: 'readDocxText',
910
- description:
911
- '读取 Word 文档(.docx)并提取纯文本内容。参数:filePath 为文档路径。返回值:对象,包含 success、data(含 filePath、text)、error。',
912
- parameters: {
913
- type: 'object',
914
- properties: {
915
- filePath: { type: 'string', description: '要读取的 .docx 文件路径。' },
916
- },
917
- required: ['filePath'],
918
- },
919
- },
920
- },
921
- {
922
- type: 'function',
923
- function: {
924
- name: 'readDocxHtml',
925
- description:
926
- '将 Word 文档(.docx)转换为 HTML 字符串,保留格式信息(粗体、斜体、表格等)。参数:filePath 为文档路径。返回值:对象,包含 success、data(含 filePath、html)、error。',
927
- parameters: {
928
- type: 'object',
929
- properties: {
930
- filePath: { type: 'string', description: '要读取的 .docx 文件路径。' },
931
- },
932
- required: ['filePath'],
933
- },
934
- },
935
- },
936
- {
937
- type: 'function',
938
- function: {
939
- name: 'getDocxInfo',
940
- description:
941
- '获取 Word 文档的基础信息。参数:filePath 为文档路径。返回值:对象,包含 success、data(含 filePath、size、mtime、wordCount、charCount、lineCount、paragraphCount)、error。',
942
- parameters: {
943
- type: 'object',
944
- properties: {
945
- filePath: { type: 'string', description: '要查询的 .docx 文件路径。' },
946
- },
947
- required: ['filePath'],
948
- },
949
- },
950
- },
951
- {
952
- type: 'function',
953
- function: {
954
- name: 'searchDocxText',
955
- description:
956
- '在 Word 文档中搜索指定关键词,返回所有匹配行。参数:filePath 为文档路径;keyword 为搜索关键词。返回值:对象,包含 success、data(含 keyword、matchCount、matches 数组)、error。',
957
- parameters: {
958
- type: 'object',
959
- properties: {
960
- filePath: { type: 'string', description: '要搜索的 .docx 文件路径。' },
961
- keyword: { type: 'string', description: '搜索关键词。' },
962
- },
963
- required: ['filePath', 'keyword'],
964
- },
965
- },
966
- },
967
- {
968
- type: 'function',
969
- function: {
970
- name: 'docxToText',
971
- description:
972
- '将 Word 文档导出为纯文本文件(.txt)。参数:inputPath 为源 .docx 路径;outputPath 为输出 .txt 路径。返回值:对象,包含 success、data(含 inputPath、outputPath)、error。',
973
- parameters: {
974
- type: 'object',
975
- properties: {
976
- inputPath: { type: 'string', description: '源 .docx 文件路径。' },
977
- outputPath: { type: 'string', description: '输出 .txt 文件路径。' },
978
- },
979
- required: ['inputPath', 'outputPath'],
980
- },
981
- },
982
- },
983
- {
984
- type: 'function',
985
- function: {
986
- name: 'docxToHtml',
987
- description:
988
- '将 Word 文档导出为 HTML 文件。参数:inputPath 为源 .docx 路径;outputPath 为输出 .html 路径。返回值:对象,包含 success、data(含 inputPath、outputPath)、error。',
989
- parameters: {
990
- type: 'object',
991
- properties: {
992
- inputPath: { type: 'string', description: '源 .docx 文件路径。' },
993
- outputPath: { type: 'string', description: '输出 .html 文件路径。' },
994
- },
995
- required: ['inputPath', 'outputPath'],
996
- },
997
- },
998
- },
999
- {
1000
- type: 'function',
1001
- function: {
1002
- name: 'createDocx',
1003
- description: `创建一个新的 Word 文档(.docx),支持添加多种内容块。
1004
- 参数:filePath 为输出路径;sections 为内容数组,每项格式如下:
1005
- - 段落:{ type:'paragraph', text, bold, italic, alignment, color, fontSize, runs:[{text,bold,italic,underline,color,fontSize}] }
1006
- - 标题:{ type:'heading', text, level(1-6), alignment }
1007
- - 表格:{ type:'table', rows:[[cell,...]] }
1008
- - 无序列表:{ type:'list', items:[...], level(0-8) }
1009
- - 有序列表:{ type:'numberedList', items:[...] }
1010
- - 分页符:{ type:'pageBreak' }
1011
- - 分隔线:{ type:'horizontalRule' }
1012
- 返回值:对象,包含 success、data(含 filePath、sectionCount)、error。`,
1013
- parameters: {
1014
- type: 'object',
1015
- properties: {
1016
- filePath: { type: 'string', description: '输出 .docx 文件路径。' },
1017
- sections: {
1018
- type: 'array',
1019
- description: '内容块数组,支持 paragraph/heading/table/list/numberedList/pageBreak/horizontalRule 类型。',
1020
- items: { type: 'object' },
1021
- },
1022
- },
1023
- required: ['filePath'],
1024
- },
1025
- },
1026
- },
1027
- {
1028
- type: 'function',
1029
- function: {
1030
- name: 'appendToDocx',
1031
- description:
1032
- '向已有 Word 文档末尾追加新内容块。参数:filePath 为目标 .docx 路径;sections 为要追加的内容块数组(格式同 createDocx)。返回值:对象,包含 success、data(含 filePath)、error。',
1033
- parameters: {
1034
- type: 'object',
1035
- properties: {
1036
- filePath: { type: 'string', description: '目标 .docx 文件路径。' },
1037
- sections: { type: 'array', description: '要追加的内容块数组。', items: { type: 'object' } },
1038
- },
1039
- required: ['filePath', 'sections'],
1040
- },
1041
- },
1042
- },
1043
- {
1044
- type: 'function',
1045
- function: {
1046
- name: 'overwriteDocx',
1047
- description:
1048
- '用新内容块完全替换已有 Word 文档的内容。参数:filePath 为目标 .docx 路径;sections 为新内容块数组(格式同 createDocx)。返回值:对象,包含 success、data(含 filePath)、error。',
1049
- parameters: {
1050
- type: 'object',
1051
- properties: {
1052
- filePath: { type: 'string', description: '目标 .docx 文件路径。' },
1053
- sections: { type: 'array', description: '新内容块数组。', items: { type: 'object' } },
1054
- },
1055
- required: ['filePath', 'sections'],
1056
- },
1057
- },
1058
- },
1059
- {
1060
- type: 'function',
1061
- function: {
1062
- name: 'replaceDocxText',
1063
- description:
1064
- '在 Word 文档中进行全局文本替换(包括正文、页眉、页脚)。参数:filePath 为目标 .docx 路径;searchText 为要查找的文本;replaceText 为替换后的文本。返回值:对象,包含 success、data(含 filePath、replaceCount)、error。',
1065
- parameters: {
1066
- type: 'object',
1067
- properties: {
1068
- filePath: { type: 'string', description: '目标 .docx 文件路径。' },
1069
- searchText: { type: 'string', description: '要查找的文本。' },
1070
- replaceText: { type: 'string', description: '替换后的文本。' },
1071
- },
1072
- required: ['filePath', 'searchText', 'replaceText'],
1073
- },
1074
- },
1075
- },
1076
- {
1077
- type: 'function',
1078
- function: {
1079
- name: 'fillDocxTemplate',
1080
- description: `用数据填充 Word 模板文档,生成新文档。模板中使用 {变量名} 作为占位符。
1081
- 参数:templatePath 为模板 .docx 路径;outputPath 为输出路径;data 为键值对象(如 { name:'张三', date:'2026-01-01' })。
1082
- 返回值:对象,包含 success、data(含 templatePath、outputPath)、error。`,
1083
- parameters: {
1084
- type: 'object',
1085
- properties: {
1086
- templatePath: { type: 'string', description: '模板 .docx 文件路径。' },
1087
- outputPath: { type: 'string', description: '输出 .docx 文件路径。' },
1088
- data: { type: 'object', description: '模板变量键值对,如 { title: "标题" }。' },
1089
- },
1090
- required: ['templatePath', 'outputPath', 'data'],
1091
- },
1092
- },
1093
- },
1094
- {
1095
- type: 'function',
1096
- function: {
1097
- name: 'mergeDocx',
1098
- description:
1099
- '将多个 Word 文档合并为一个,各文档之间以分页符分隔。参数:inputPaths 为源文件路径数组;outputPath 为输出路径。返回值:对象,包含 success、data(含 outputPath)、error。',
1100
- parameters: {
1101
- type: 'object',
1102
- properties: {
1103
- inputPaths: { type: 'array', items: { type: 'string' }, description: '要合并的 .docx 文件路径数组。' },
1104
- outputPath: { type: 'string', description: '合并后输出的 .docx 文件路径。' },
1105
- },
1106
- required: ['inputPaths', 'outputPath'],
1107
- },
1108
- },
1109
- },
1110
- {
1111
- type: 'function',
1112
- function: {
1113
- name: 'extractDocxLinks',
1114
- description:
1115
- '提取 Word 文档中所有的超链接 URL。参数:filePath 为 .docx 文件路径。返回值:对象,包含 success、data(含 filePath、links 数组)、error。',
1116
- parameters: {
1117
- type: 'object',
1118
- properties: {
1119
- filePath: { type: 'string', description: '要提取链接的 .docx 文件路径。' },
1120
- },
1121
- required: ['filePath'],
1122
- },
1123
- },
1124
- },
1125
- {
1126
- type: 'function',
1127
- function: {
1128
- name: 'getDocxParagraphStats',
1129
- description:
1130
- '统计 Word 文档各段落的字数与字符数。参数:filePath 为 .docx 文件路径。返回值:对象,包含 success、data(含 paragraphCount、totalWordCount、paragraphs 数组)、error。',
1131
- parameters: {
1132
- type: 'object',
1133
- properties: {
1134
- filePath: { type: 'string', description: '要统计的 .docx 文件路径。' },
1135
- },
1136
- required: ['filePath'],
1137
- },
1138
- },
1139
- },
1140
- {
1141
- type: 'function',
1142
- function: {
1143
- name: 'wordToPdf',
1144
- description:
1145
- '将 Word 文档(.docx)转换为 PDF 文件。依赖 puppeteer,转换时保留基础格式。参数:inputPath 为源 .docx 路径;outputPath 为输出 .pdf 路径。返回值:对象,包含 success、data(含 inputPath、outputPath)、error。',
1146
- parameters: {
1147
- type: 'object',
1148
- properties: {
1149
- inputPath: { type: 'string', description: '源 .docx 文件路径。' },
1150
- outputPath: { type: 'string', description: '输出 .pdf 文件路径。' },
1151
- },
1152
- required: ['inputPath', 'outputPath'],
1153
- },
1154
- },
1155
- },
1156
- {
1157
- type: 'function',
1158
- function: {
1159
- name: 'wordToHtml',
1160
- description:
1161
- '将 Word 文档(.docx)转换为 HTML 文件,保留格式信息(粗体、斜体、表格等)。参数:inputPath 为源 .docx 路径;outputPath 为输出 .html 路径。返回值:对象,包含 success、data(含 inputPath、outputPath、messages)、error。',
1162
- parameters: {
1163
- type: 'object',
1164
- properties: {
1165
- inputPath: { type: 'string', description: '源 .docx 文件路径。' },
1166
- outputPath: { type: 'string', description: '输出 .html 文件路径。' },
1167
- },
1168
- required: ['inputPath', 'outputPath'],
1169
- },
1170
- },
1171
- },
1172
- {
1173
- type: 'function',
1174
- function: {
1175
- name: 'wordToMarkdown',
1176
- description:
1177
- '将 Word 文档(.docx)转换为 Markdown 文件(.md)。通过 HTML 中间格式进行转换,保留标题、段落、表格、链接等结构。参数:inputPath 为源 .docx 路径;outputPath 为输出 .md 路径。返回值:对象,包含 success、data(含 inputPath、outputPath)、error。',
1178
- parameters: {
1179
- type: 'object',
1180
- properties: {
1181
- inputPath: { type: 'string', description: '源 .docx 文件路径。' },
1182
- outputPath: { type: 'string', description: '输出 .md 文件路径。' },
1183
- },
1184
- required: ['inputPath', 'outputPath'],
1185
- },
1186
- },
1187
- },
1188
- {
1189
- type: 'function',
1190
- function: {
1191
- name: 'markdownToWord',
1192
- description:
1193
- '将 Markdown 文件(.md)转换为 Word 文档(.docx)。支持标题、段落、表格、列表、代码块、引用等元素。参数:inputPath 为源 .md 路径;outputPath 为输出 .docx 路径。返回值:对象,包含 success、data(含 inputPath、outputPath、sectionCount)、error。',
1194
- parameters: {
1195
- type: 'object',
1196
- properties: {
1197
- inputPath: { type: 'string', description: '源 .md 文件路径。' },
1198
- outputPath: { type: 'string', description: '输出 .docx 文件路径。' },
1199
- },
1200
- required: ['inputPath', 'outputPath'],
1201
- },
1202
- },
1203
- },
1204
- {
1205
- type: 'function',
1206
- function: {
1207
- name: 'htmlToWord',
1208
- description:
1209
- '将 HTML 文件转换为 Word 文档(.docx),解析标题、段落、表格、列表等元素。参数:inputPath 为源 .html 路径;outputPath 为输出 .docx 路径。返回值:对象,包含 success、data(含 inputPath、outputPath、sectionCount)、error。',
1210
- parameters: {
1211
- type: 'object',
1212
- properties: {
1213
- inputPath: { type: 'string', description: '源 .html 文件路径。' },
1214
- outputPath: { type: 'string', description: '输出 .docx 文件路径。' },
1215
- },
1216
- required: ['inputPath', 'outputPath'],
1217
- },
1218
- },
1219
- },
1220
- {
1221
- type: 'function',
1222
- function: {
1223
- name: 'patchDocxText',
1224
- description: `保留原格式修改 Word 文档内容(支持跨 run 文本替换)。与 replaceDocxText 的区别:本函数在替换时智能处理 Word XML 的 run 拆分问题,替换文本会沿用匹配位置的原有字符样式(字体、字号、颜色等),不重建文档结构,最大程度保留原有格式。
1225
- 参数:filePath 为目标 .docx 路径;replacements 为替换规则数组,每项格式 { search: '查找文本', replace: '替换文本' }。
1226
- 返回值:对象,包含 success、data(含 filePath、totalReplacements)、error。`,
1227
- parameters: {
1228
- type: 'object',
1229
- properties: {
1230
- filePath: { type: 'string', description: '目标 .docx 文件路径。' },
1231
- replacements: {
1232
- type: 'array',
1233
- description: '替换规则数组,每项包含 search(查找文本)和 replace(替换文本)。',
1234
- items: {
1235
- type: 'object',
1236
- properties: {
1237
- search: { type: 'string', description: '要查找的文本。' },
1238
- replace: { type: 'string', description: '替换后的文本。' },
1239
- },
1240
- required: ['search', 'replace'],
1241
- },
1242
- },
1243
- },
1244
- required: ['filePath', 'replacements'],
1245
- },
1246
- },
1247
- },
1248
- ]
1249
-
1250
- // ─── 导出 ──────────────────────────────────────────────────────────────────────
1251
-
1252
- const functions = {
1253
- docxReadme,
1254
- readDocxText,
1255
- readDocxHtml,
1256
- getDocxInfo,
1257
- searchDocxText,
1258
- docxToText,
1259
- docxToHtml,
1260
- createDocx,
1261
- appendToDocx,
1262
- overwriteDocx,
1263
- replaceDocxText,
1264
- fillDocxTemplate,
1265
- mergeDocx,
1266
- extractDocxLinks,
1267
- getDocxParagraphStats,
1268
- wordToPdf,
1269
- wordToHtml,
1270
- wordToMarkdown,
1271
- markdownToWord,
1272
- htmlToWord,
1273
- patchDocxText,
1274
- }
1275
-
1276
- const DocxTool = {
1277
- name: 'DocxTool',
1278
- description: '提供 Word 文档(.docx)的创建、读取、搜索、替换、模板填充、格式转换、合并,以及 Word/Markdown/HTML/PDF 格式互转等全面处理能力',
1279
- // 格式转换:wordToPdf、wordToHtml、wordToMarkdown、markdownToWord、htmlToWord
1280
- platform: 'all',
1281
- descriptions,
1282
- functions,
1283
- isSystem: true
1284
- }
1285
-
1286
- module.exports = DocxTool