@dyno181cm.nexsoft/zentao_mcp 1.2.12 → 1.2.14

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 (2) hide show
  1. package/build/tools.js +20 -28
  2. package/package.json +1 -1
package/build/tools.js CHANGED
@@ -53,45 +53,39 @@ function toFileUrl(filePath) {
53
53
  }
54
54
  return `file:///${normalized}`;
55
55
  }
56
- /** Convert a local file to a Base64 data URI for safe rendering in browser/chat UI. */
57
- function fileToBase64(filePath) {
58
- try {
59
- const data = fs.readFileSync(filePath);
60
- const ext = path.extname(filePath).toLowerCase().replace('.', '');
61
- const mime = ext === 'svg' ? 'image/svg+xml' : `image/${ext || 'png'}`;
62
- return `data:${mime};base64,${data.toString('base64')}`;
63
- }
64
- catch {
65
- return toFileUrl(filePath); // fallback to file URL if read fails
66
- }
67
- }
68
56
  /**
69
- * Find all <img src="..."> URLs in an HTML string, download each image
57
+ * Find all <img src="..."> tags in an HTML string, download each image
70
58
  * to the local tmp directory using the authenticated ZenTao client,
71
- * and replace the remote src with the local Base64 data URI.
72
- * Images already cached on disk are not re-downloaded.
59
+ * and replace the entire <img> tag with an <a> link pointing to the local file:// URL.
60
+ * This saves AI tokens (no huge Base64 data) and avoids local file load errors in UI.
73
61
  */
74
62
  async function localizeImages(html) {
75
63
  if (!html)
76
64
  return html;
77
- const imgRegex = /<img[^>]+src=["']([^"']+)["']/gi;
78
- const matches = [...html.matchAll(imgRegex)];
65
+ const imgTagRegex = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
66
+ const matches = [...html.matchAll(imgTagRegex)];
79
67
  if (matches.length === 0)
80
68
  return html;
81
69
  let result = html;
82
70
  await Promise.all(matches.map(async (match) => {
71
+ const fullTag = match[0];
83
72
  const remoteUrl = match[1];
84
- // Derive a stable local filename from the last path segment of the URL
85
- const filename = `zentao_img_${path.basename(remoteUrl.split('?')[0])}`;
86
- const localPath = path.join(os.tmpdir(), filename);
73
+ const altMatch = fullTag.match(/alt=["']([^"']+)["']/i);
74
+ const filename = path.basename(remoteUrl.split('?')[0]);
75
+ const linkText = altMatch ? altMatch[1] : filename;
76
+ const localFilename = `zentao_img_${filename}`;
77
+ const localPath = path.join(os.tmpdir(), localFilename);
87
78
  try {
88
79
  if (!fs.existsSync(localPath)) {
89
80
  await client.downloadImageToLocal(remoteUrl, localPath);
90
81
  }
91
- result = result.split(remoteUrl).join(fileToBase64(localPath));
82
+ const fileUrl = toFileUrl(localPath);
83
+ // Replace img tag with a standard hyperlink (no inline rendering, saves tokens)
84
+ const linkTag = `<a href="${fileUrl}">📎 ${linkText}</a>`;
85
+ result = result.split(fullTag).join(linkTag);
92
86
  }
93
87
  catch {
94
- // If download fails, keep the original remote URL
88
+ // Keep original tag if download fails
95
89
  }
96
90
  }));
97
91
  return result;
@@ -144,10 +138,6 @@ async function renderAttachments(files) {
144
138
  const lines = enriched.map((f) => {
145
139
  if (!f.localPath)
146
140
  return `- 📎 ${f.title} — *(download failed, Size: ${formatSize(f.size)})*`;
147
- if (IMAGE_EXTS.has(f.ext)) {
148
- const base64 = fileToBase64(f.localPath);
149
- return `- 📎 ![${f.title}](${base64}) *(Size: ${formatSize(f.size)})*`;
150
- }
151
141
  const fileUrl = toFileUrl(f.localPath);
152
142
  return `- 📎 [${f.title}](${fileUrl}) *(Size: ${formatSize(f.size)})*`;
153
143
  });
@@ -183,10 +173,11 @@ export async function taskToMarkdown(rawTask) {
183
173
  const localizedDesc = await localizeImages(rawTask.desc);
184
174
  const desc = htmlToMarkdown(localizedDesc);
185
175
  const attachments = await renderAttachments(parseFiles(rawTask.files));
176
+ const metaList = meta.map(m => `- ${m}`).join('\n');
186
177
  return [
187
178
  `# Task #${rawTask.id}: ${rawTask.name}`,
188
179
  '',
189
- meta.join(' | '),
180
+ metaList,
190
181
  '',
191
182
  '## Description',
192
183
  desc || '*No description provided.*',
@@ -219,10 +210,11 @@ export async function bugToMarkdown(rawBug) {
219
210
  const localizedSteps = await localizeImages(rawBug.steps);
220
211
  const steps = htmlToMarkdown(localizedSteps);
221
212
  const attachments = await renderAttachments(parseFiles(rawBug.files));
213
+ const metaList = meta.map(m => `- ${m}`).join('\n');
222
214
  return [
223
215
  `# Bug #${rawBug.id}: ${rawBug.title}`,
224
216
  '',
225
- meta.join(' | '),
217
+ metaList,
226
218
  '',
227
219
  '## Repro Steps',
228
220
  steps || '*No steps provided.*',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dyno181cm.nexsoft/zentao_mcp",
3
- "version": "1.2.12",
3
+ "version": "1.2.14",
4
4
  "description": "Zentao MCP Server",
5
5
  "repository": {
6
6
  "type": "git",