]> acesimba.cloud Git - StationBuilder.git/commitdiff
领域知识上传支持 Word/Excel/PDF/图片:提取正文注入提示词
authorroot <root@localhost.localdomain>
Thu, 20 Aug 2026 11:23:37 +0000 (19:23 +0800)
committerroot <root@localhost.localdomain>
Thu, 20 Aug 2026 11:23:37 +0000 (19:23 +0800)
- 后端 db-sidecar.mjs:新增 async extractDocText 替换 isTextMime/readKnowledgeFile,
  PDF(pdf-parse)/Word(.docx,mammoth)/Excel(.xls/.xlsx,SheetJS) 提取正文并 16k 截断注入,
  图片及老 .doc 返回空串仅登记为参考来源;buildKnowledgePrompt 改为 await extractDocText。
- 前端 KnowledgeStep:上传 accept 放开至 .pdf/.doc/.docx/.xls/.xlsx/image/* 等,
  提示文案补充「Word/Excel/PDF 提取正文,图片仅登记」,文件类徽标「文档」→「文件」。
- 新增依赖 pdf-parse/mammoth/xlsx(纯 JS,无原生编译)。
- design-doc.html 升 v0.23 并登记变更。

app/page.tsx
db-sidecar.mjs
package-lock.json
package.json
public/design-doc.html

index e1aee99b4c233532cc06fcf799aecdc33844e6b6..94ab8ca78ed592c12f9a0261ec725a2f44c20e49 100644 (file)
@@ -1790,10 +1790,10 @@ function KnowledgeStep({id,docs,notify,reload,confirm}:{id:number;docs:any[];not
   const removeDoc=async(docId:number)=>{ if(!(await confirm("确定删除该知识来源?此操作不可恢复"))) return; try{ await fetchDb(`/knowledge-docs/${docId}`,{method:"DELETE"}); notify("已删除"); reload(); }catch(e:any){ notify("删除失败:"+(e?.message||e)); } };
   return <div className="two-column">
     <Panel title="上传领域知识文档" tools={busy?<Status>上传中…</Status>:undefined}>
-      <p className="hint">上传与本站领域相关的文档(标准规范、设备手册、工艺说明、建设方案等)。知识文本将作为资产配置、功能菜单 AI 补全的优先参考资料,并随方案保存。支持 txt / md / json / csv / html 等文本类,其他格式将作为参考资料登记。</p>
+      <p className="hint">上传与本站领域相关的文档(标准规范、设备手册、工艺说明、建设方案等)。知识文本将作为资产配置、功能菜单 AI 补全的优先参考资料,并随方案保存。支持 Word / Excel / PDF / 图片及常见文本格式;Word、Excel、PDF 会提取正文注入,图片仅作参考登记。</p>
       <label className="upload" style={{height:96}}>
         <b>+</b><span>{busy?"上传中…":"选择文件或拖拽到此处(可多选)"}</span>
-        <input type="file" multiple hidden accept=".txt,.md,.markdown,.json,.csv,.log,.html,.xml,.yaml,.yml,text/*" onChange={e=>uploadFiles(e.target.files)}/>
+        <input type="file" multiple hidden accept=".txt,.md,.markdown,.json,.csv,.log,.html,.xml,.yaml,.yml,.pdf,.doc,.docx,.xls,.xlsx,image/*" onChange={e=>uploadFiles(e.target.files)}/>
       </label>
     </Panel>
     <Panel title="配置参考 URL 站点">
@@ -1809,7 +1809,7 @@ function KnowledgeStep({id,docs,notify,reload,confirm}:{id:number;docs:any[];not
         <table className="data-table"><thead><tr><th>名称</th><th>类型</th><th>大小</th><th>创建时间</th><th>操作</th></tr></thead>
         <tbody>{docs.map((d:any)=><tr key={d.id}>
           <td>{d.name}</td>
-          <td><span style={{padding:"2px 8px",borderRadius:4,fontSize:12,background:"#2a3a55",color:"#cfe8ff"}}>{d.doc_type==="link"?"URL 站点":"文档"}</span></td>
+          <td><span style={{padding:"2px 8px",borderRadius:4,fontSize:12,background:"#2a3a55",color:"#cfe8ff"}}>{d.doc_type==="link"?"URL 站点":"文件"}</span></td>
           <td>{d.size!=null?`${Math.round(Number(d.size)/1024*10)/10} KB`:"—"}</td>
           <td>{d.created_at||""}</td>
           <td style={{display:"flex",gap:8}}>{d.doc_type==="file"&&<a className="text-button" href={`/api/db/knowledge-docs/${d.id}/file`} target="_blank" rel="noreferrer">下载</a>}<button className="text-button" onClick={()=>removeDoc(d.id)}>删除</button></td>
index ff3c8d60a3c221e58a582f8e02b70f6d06fc886e..0f15f23f24206f1046acd2a442d9f5f2e2acc40c 100644 (file)
@@ -9,6 +9,9 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
 import { dirname, join, basename, extname } from "node:path";
 import { fileURLToPath } from "node:url";
 import { randomUUID } from "node:crypto";
+import mammoth from "mammoth";
+import * as XLSX from "xlsx";
+import { PDFParse } from "pdf-parse";
 
 const PORT = 37823;
 const PROJECT_DIR = dirname(fileURLToPath(import.meta.url));
@@ -1111,18 +1114,59 @@ async function handleRefineCube(req, res, siteId, cubeId) {
 
   // ===== AI 智能补全:基于站点名称/领域/描述分析并生成「补充描述」 =====
   // ===== 领域知识范本:从本方案已上传/配置的领域知识(文档 / URL 站点)中抽取可注入提示词的文本 =====
-  function isTextMime(mime, filename) {
-    if (mime && String(mime).toLowerCase().startsWith("text/")) return true;
-    const ext = (filename || "").toLowerCase().split(".").pop();
-    return [".txt", ".md", ".markdown", ".json", ".csv", ".log", ".html", ".xml", ".yaml", ".yml"].includes("." + ext);
+  // 文本类(txt/md/json/csv/html/xml/yaml/yml)直接读取;pdf/docx/xls/xlsx 提取正文;
+  // 图片及无法提取的格式返回空串(buildKnowledgePrompt 中仍会登记为参考来源)。
+  const TEXT_EXTS = [".txt", ".md", ".markdown", ".json", ".csv", ".log", ".html", ".htm", ".xml", ".yaml", ".yml", ".text"];
+  const IMAGE_EXTS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp", ".svg", ".tiff", ".tif"];
+  const MAX_KB_TEXT = 16000;
+  function truncateText(text) {
+    return text.length > MAX_KB_TEXT ? text.slice(0, MAX_KB_TEXT) + "\n…(内容已截断,原文较长)" : text;
   }
-  function readKnowledgeFile(filePath, mime, filename) {
+  async function extractDocText(filePath, mime, filename) {
     try {
       if (!filePath || !existsSync(filePath)) return "";
-      if (!isTextMime(mime, filename)) return "";
-      const text = readFileSync(filePath, "utf8");
-      return text.length > 16000 ? text.slice(0, 16000) + "\n…(内容已截断,原文较长)" : text;
-    } catch { return ""; }
+      const ext = "." + String(filename || "").toLowerCase().split(".").pop();
+      const lowerMime = String(mime || "").toLowerCase();
+
+      // 图片:仅登记,不提取
+      if (lowerMime.startsWith("image/") || IMAGE_EXTS.includes(ext)) return "";
+
+      // 纯文本类:直接读取
+      if (lowerMime.startsWith("text/") || TEXT_EXTS.includes(ext)) {
+        return truncateText(readFileSync(filePath, "utf8"));
+      }
+
+      const buf = readFileSync(filePath);
+      // PDF
+      if (ext === ".pdf" || lowerMime === "application/pdf") {
+        const parser = new PDFParse({ data: buf });
+        try {
+          const result = await parser.getText();
+          return truncateText(result.text || "");
+        } finally {
+          await parser.destroy().catch(() => {});
+        }
+      }
+      // Word(仅 .docx;老 .doc 二进制无法纯 JS 提取,作为参考登记)
+      if (ext === ".docx" || lowerMime === "application/vnd.openxmlformats-officedocument.wordprocessingml.document") {
+        const r = await mammoth.extractRawText({ buffer: buf });
+        return truncateText(r.value || "");
+      }
+      // Excel(.xls / .xlsx 均可由 SheetJS 读取)
+      if (ext === ".xls" || ext === ".xlsx" || lowerMime.includes("spreadsheet") || lowerMime.includes("excel")) {
+        const wb = XLSX.read(buf, { type: "buffer" });
+        const parts = wb.SheetNames.map((name) => {
+          const csv = XLSX.utils.sheet_to_csv(wb.Sheets[name], { blankrows: false });
+          return csv ? `【Sheet: ${name}】\n${csv}` : "";
+        }).filter(Boolean);
+        return truncateText(parts.join("\n\n"));
+      }
+
+      // 其它二进制(含老 .doc 等):仅登记,不提取
+      return "";
+    } catch {
+      return "";
+    }
   }
   async function fetchKnowledgeUrl(url) {
     try {
@@ -1146,7 +1190,7 @@ async function handleRefineCube(req, res, siteId, cubeId) {
     const parts = [];
     for (const d of docs) {
       let body = "";
-      if (d.doc_type === "file") body = readKnowledgeFile(d.file_path, d.mime, d.name);
+      if (d.doc_type === "file") body = await extractDocText(d.file_path, d.mime, d.name);
       else if (d.doc_type === "link") body = await fetchKnowledgeUrl(d.url);
       if (body && body.trim()) parts.push(`【领域知识 · ${d.name}】\n${body.trim()}\n`);
       else parts.push(`(参考来源:${d.doc_type === "link" ? d.url : d.name})`);
index acef9602a27fd4af8c05bc3e27104544b8ed8ca5..60cc0a540feb50ff788dec89eefac63db8805235 100644 (file)
@@ -9,9 +9,12 @@
       "version": "0.1.0",
       "dependencies": {
         "drizzle-orm": "0.45.2",
+        "mammoth": "^1.12.1",
         "next": "16.2.6",
+        "pdf-parse": "^2.4.5",
         "react": "19.2.6",
-        "react-dom": "19.2.6"
+        "react-dom": "19.2.6",
+        "xlsx": "^0.18.5"
       },
       "devDependencies": {
         "@cloudflare/vite-plugin": "1.37.1",
         "@jridgewell/sourcemap-codec": "^1.4.14"
       }
     },
+    "node_modules/@napi-rs/canvas": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas/-/canvas-0.1.80.tgz",
+      "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==",
+      "license": "MIT",
+      "workspaces": [
+        "e2e/*"
+      ],
+      "engines": {
+        "node": ">= 10"
+      },
+      "optionalDependencies": {
+        "@napi-rs/canvas-android-arm64": "0.1.80",
+        "@napi-rs/canvas-darwin-arm64": "0.1.80",
+        "@napi-rs/canvas-darwin-x64": "0.1.80",
+        "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80",
+        "@napi-rs/canvas-linux-arm64-gnu": "0.1.80",
+        "@napi-rs/canvas-linux-arm64-musl": "0.1.80",
+        "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80",
+        "@napi-rs/canvas-linux-x64-gnu": "0.1.80",
+        "@napi-rs/canvas-linux-x64-musl": "0.1.80",
+        "@napi-rs/canvas-win32-x64-msvc": "0.1.80"
+      }
+    },
+    "node_modules/@napi-rs/canvas-android-arm64": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz",
+      "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-darwin-arm64": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz",
+      "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-darwin-x64": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz",
+      "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz",
+      "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm64-gnu": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz",
+      "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-arm64-musl": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz",
+      "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz",
+      "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==",
+      "cpu": [
+        "riscv64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-x64-gnu": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz",
+      "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-linux-x64-musl": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz",
+      "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@napi-rs/canvas-win32-x64-msvc": {
+      "version": "0.1.80",
+      "resolved": "http://mirrors.tencent.com/npm/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz",
+      "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
     "node_modules/@napi-rs/wasm-runtime": {
       "version": "0.2.12",
       "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
         "@xtuc/long": "4.2.2"
       }
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.8.14",
+      "resolved": "http://mirrors.tencent.com/npm/@xmldom/xmldom/-/xmldom-0.8.14.tgz",
+      "integrity": "sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/@xtuc/ieee754": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
         "node": ">=0.4.0"
       }
     },
+    "node_modules/adler-32": {
+      "version": "1.3.1",
+      "resolved": "http://mirrors.tencent.com/npm/adler-32/-/adler-32-1.3.1.tgz",
+      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.15.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/bluebird": {
+      "version": "3.4.7",
+      "resolved": "http://mirrors.tencent.com/npm/bluebird/-/bluebird-3.4.7.tgz",
+      "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==",
+      "license": "MIT"
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.14",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
       ],
       "license": "CC-BY-4.0"
     },
+    "node_modules/cfb": {
+      "version": "1.2.2",
+      "resolved": "http://mirrors.tencent.com/npm/cfb/-/cfb-1.2.2.tgz",
+      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "crc-32": "~1.2.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
       "license": "MIT"
     },
+    "node_modules/codepage": {
+      "version": "1.15.0",
+      "resolved": "http://mirrors.tencent.com/npm/codepage/-/codepage-1.15.0.tgz",
+      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "http://mirrors.tencent.com/npm/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "license": "MIT"
+    },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "http://mirrors.tencent.com/npm/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "license": "Apache-2.0",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
         "node": ">=8"
       }
     },
+    "node_modules/dingbat-to-unicode": {
+      "version": "1.0.1",
+      "resolved": "http://mirrors.tencent.com/npm/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
+      "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==",
+      "license": "BSD-2-Clause"
+    },
     "node_modules/doctrine": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
         }
       }
     },
+    "node_modules/duck": {
+      "version": "0.1.12",
+      "resolved": "http://mirrors.tencent.com/npm/duck/-/duck-0.1.12.tgz",
+      "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
+      "license": "BSD",
+      "dependencies": {
+        "underscore": "^1.13.1"
+      }
+    },
     "node_modules/dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/frac": {
+      "version": "1.1.2",
+      "resolved": "http://mirrors.tencent.com/npm/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/fsevents": {
       "version": "2.3.3",
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
         "node": ">=16.x"
       }
     },
+    "node_modules/immediate": {
+      "version": "3.0.6",
+      "resolved": "http://mirrors.tencent.com/npm/immediate/-/immediate-3.0.6.tgz",
+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+      "license": "MIT"
+    },
     "node_modules/import-fresh": {
       "version": "3.3.1",
       "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
         "node": ">=0.8.19"
       }
     },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "http://mirrors.tencent.com/npm/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
     "node_modules/internal-slot": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
         "node": ">=4.0"
       }
     },
+    "node_modules/jszip": {
+      "version": "3.10.1",
+      "resolved": "http://mirrors.tencent.com/npm/jszip/-/jszip-3.10.1.tgz",
+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+      "license": "(MIT OR GPL-3.0-or-later)",
+      "dependencies": {
+        "lie": "~3.3.0",
+        "pako": "~1.0.2",
+        "readable-stream": "~2.3.6",
+        "setimmediate": "^1.0.5"
+      }
+    },
+    "node_modules/jszip/node_modules/pako": {
+      "version": "1.0.11",
+      "resolved": "http://mirrors.tencent.com/npm/pako/-/pako-1.0.11.tgz",
+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+      "license": "(MIT AND Zlib)"
+    },
     "node_modules/keyv": {
       "version": "4.5.4",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/lie": {
+      "version": "3.3.0",
+      "resolved": "http://mirrors.tencent.com/npm/lie/-/lie-3.3.0.tgz",
+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+      "license": "MIT",
+      "dependencies": {
+        "immediate": "~3.0.5"
+      }
+    },
     "node_modules/lightningcss": {
       "version": "1.31.1",
       "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz",
         "loose-envify": "cli.js"
       }
     },
+    "node_modules/lop": {
+      "version": "0.4.2",
+      "resolved": "http://mirrors.tencent.com/npm/lop/-/lop-0.4.2.tgz",
+      "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==",
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "duck": "^0.1.12",
+        "option": "~0.2.1",
+        "underscore": "^1.13.1"
+      }
+    },
     "node_modules/lru-cache": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
         "@jridgewell/sourcemap-codec": "^1.5.5"
       }
     },
+    "node_modules/mammoth": {
+      "version": "1.12.1",
+      "resolved": "http://mirrors.tencent.com/npm/mammoth/-/mammoth-1.12.1.tgz",
+      "integrity": "sha512-nCH9KKjWi3jQ+i8bUKs7k1yrXtSEGpWgF8IYkzsFMcbn+5S6l4bZEBbyx2hOQErFiXPuAs9RPa6qjXVxhyx/8g==",
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "@xmldom/xmldom": "^0.8.6",
+        "argparse": "~1.0.3",
+        "base64-js": "^1.5.1",
+        "bluebird": "~3.4.0",
+        "dingbat-to-unicode": "^1.0.1",
+        "jszip": "^3.7.1",
+        "lop": "^0.4.2",
+        "path-is-absolute": "^1.0.0",
+        "underscore": "^1.13.1",
+        "xmlbuilder": "^10.0.0"
+      },
+      "bin": {
+        "mammoth": "bin/mammoth"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/mammoth/node_modules/argparse": {
+      "version": "1.0.10",
+      "resolved": "http://mirrors.tencent.com/npm/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "license": "MIT",
+      "dependencies": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "node_modules/mammoth/node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "http://mirrors.tencent.com/npm/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
     "node_modules/math-intrinsics": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/option": {
+      "version": "0.2.4",
+      "resolved": "http://mirrors.tencent.com/npm/option/-/option-0.2.4.tgz",
+      "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==",
+      "license": "BSD-2-Clause"
+    },
     "node_modules/optionator": {
       "version": "0.9.4",
       "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
         "node": ">=8"
       }
     },
+    "node_modules/path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "http://mirrors.tencent.com/npm/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/path-key": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/pdf-parse": {
+      "version": "2.4.5",
+      "resolved": "http://mirrors.tencent.com/npm/pdf-parse/-/pdf-parse-2.4.5.tgz",
+      "integrity": "sha512-mHU89HGh7v+4u2ubfnevJ03lmPgQ5WU4CxAVmTSh/sxVTEDYd1er/dKS/A6vg77NX47KTEoihq8jZBLr8Cxuwg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@napi-rs/canvas": "0.1.80",
+        "pdfjs-dist": "5.4.296"
+      },
+      "bin": {
+        "pdf-parse": "bin/cli.mjs"
+      },
+      "engines": {
+        "node": ">=20.16.0 <21 || >=22.3.0"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/mehmet-kozan"
+      }
+    },
+    "node_modules/pdfjs-dist": {
+      "version": "5.4.296",
+      "resolved": "http://mirrors.tencent.com/npm/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz",
+      "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=20.16.0 || >=22.3.0"
+      },
+      "optionalDependencies": {
+        "@napi-rs/canvas": "^0.1.80"
+      }
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "http://mirrors.tencent.com/npm/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "license": "MIT"
+    },
     "node_modules/prop-types": {
       "version": "15.8.1",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
         "webpack": "^5.59.0"
       }
     },
+    "node_modules/readable-stream": {
+      "version": "2.3.8",
+      "resolved": "http://mirrors.tencent.com/npm/readable-stream/-/readable-stream-2.3.8.tgz",
+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.3",
+        "isarray": "~1.0.0",
+        "process-nextick-args": "~2.0.0",
+        "safe-buffer": "~5.1.1",
+        "string_decoder": "~1.1.1",
+        "util-deprecate": "~1.0.1"
+      }
+    },
+    "node_modules/readable-stream/node_modules/isarray": {
+      "version": "1.0.0",
+      "resolved": "http://mirrors.tencent.com/npm/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+      "license": "MIT"
+    },
     "node_modules/reflect.getprototypeof": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "http://mirrors.tencent.com/npm/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "license": "MIT"
+    },
     "node_modules/safe-push-apply": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
         "node": ">= 0.4"
       }
     },
+    "node_modules/setimmediate": {
+      "version": "1.0.5",
+      "resolved": "http://mirrors.tencent.com/npm/setimmediate/-/setimmediate-1.0.5.tgz",
+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+      "license": "MIT"
+    },
     "node_modules/sharp": {
       "version": "0.34.5",
       "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
         "source-map": "^0.6.0"
       }
     },
+    "node_modules/sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "http://mirrors.tencent.com/npm/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+    },
     "node_modules/srvx": {
       "version": "0.11.15",
       "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.15.tgz",
         "node": ">=20.16.0"
       }
     },
+    "node_modules/ssf": {
+      "version": "0.11.2",
+      "resolved": "http://mirrors.tencent.com/npm/ssf/-/ssf-0.11.2.tgz",
+      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "frac": "~1.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/stable-hash": {
       "version": "0.0.5",
       "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
         "node": ">= 0.4"
       }
     },
+    "node_modules/string_decoder": {
+      "version": "1.1.1",
+      "resolved": "http://mirrors.tencent.com/npm/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
     "node_modules/string.prototype.codepointat": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/underscore": {
+      "version": "1.13.8",
+      "resolved": "http://mirrors.tencent.com/npm/underscore/-/underscore-1.13.8.tgz",
+      "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
+      "license": "MIT"
+    },
     "node_modules/undici": {
       "version": "7.24.8",
       "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.8.tgz",
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "http://mirrors.tencent.com/npm/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
     "node_modules/vinext": {
       "version": "0.0.50",
       "resolved": "https://registry.npmjs.org/vinext/-/vinext-0.0.50.tgz",
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/wmf": {
+      "version": "1.0.2",
+      "resolved": "http://mirrors.tencent.com/npm/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/word": {
+      "version": "0.3.0",
+      "resolved": "http://mirrors.tencent.com/npm/word/-/word-0.3.0.tgz",
+      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/word-wrap": {
       "version": "1.2.5",
       "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
         }
       }
     },
+    "node_modules/xlsx": {
+      "version": "0.18.5",
+      "resolved": "http://mirrors.tencent.com/npm/xlsx/-/xlsx-0.18.5.tgz",
+      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "cfb": "~1.2.1",
+        "codepage": "~1.15.0",
+        "crc-32": "~1.2.1",
+        "ssf": "~0.11.2",
+        "wmf": "~1.0.1",
+        "word": "~0.3.0"
+      },
+      "bin": {
+        "xlsx": "bin/xlsx.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/xmlbuilder": {
+      "version": "10.1.1",
+      "resolved": "http://mirrors.tencent.com/npm/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
+      "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      }
+    },
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
index 955bcb55e9f3b7b87f424bbdc44329d01a802a58..86f4ec29d311a892bbfbf0388d38153b69673c13 100644 (file)
   },
   "dependencies": {
     "drizzle-orm": "0.45.2",
+    "mammoth": "^1.12.1",
     "next": "16.2.6",
+    "pdf-parse": "^2.4.5",
     "react": "19.2.6",
-    "react-dom": "19.2.6"
+    "react-dom": "19.2.6",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@cloudflare/vite-plugin": "1.37.1",
index 322ed1275518f036d980785fc000bcf7f9020adf..051c4d35982fce0f21ecefa4e483c1dfb0b3d7e8 100644 (file)
   <nav class="doc-nav"><div class="doc-nav-inner" id="toc"></div></nav>
 
 <main id="doc">
-  <p class="muted">文档版本:v0.22 最后更新:2026-08-20 修改人:wang.yu 状态:开发中(演示数据已标注)</p>
+  <p class="muted">文档版本:v0.23 最后更新:2026-08-20 修改人:wang.yu 状态:开发中(演示数据已标注)</p>
   <h1>StationBuilder 产品与交互设计方案</h1>
 
   <h2>1. 文档说明</h2>
   <h3>11.1 版本变更记录(Changelog)</h3>
   <table class="changelog">
     <tr><th>版本</th><th>日期</th><th>修改人</th><th>说明</th></tr>
+    <tr><td>v0.23</td><td>2026-08-20</td><td>wang.yu</td><td>领域知识上传格式扩展:<code>KnowledgeStep</code> 上传 <code>accept</code> 由纯文本类放开至 Word / Excel / PDF / 图片及常见文本(<code>.pdf,.doc,.docx,.xls,.xlsx,image/*</code> 等);提示文案补充「Word、Excel、PDF 会提取正文注入,图片仅作参考登记」,列表文件类徽标「文档」改为「文件」以兼容图片。后端 <code>extractDocText(filePath,mime,filename)</code> 替换原 <code>isTextMime</code>/<code>readKnowledgeFile</code>:文本类(txt/md/json/csv/html/xml/yaml/yml 等)直接读取;PDF(<code>pdf-parse</code>)、Word(仅 <code>.docx</code>,<code>mammoth</code>)、Excel(<code>.xls</code>/<code>.xlsx</code>,SheetJS 转 CSV)提取正文并 16k 截断后注入提示词;图片及老 <code>.doc</code> 等无法纯 JS 提取的格式返回空串,<code>buildKnowledgePrompt</code> 中仍登记为参考来源(<code>(参考来源:文件名)</code>)。新增依赖 <code>pdf-parse</code> / <code>mammoth</code> / <code>xlsx</code>(纯 JS,无原生编译)。</td></tr>
     <tr><td>v0.22</td><td>2026-08-20</td><td>wang.yu</td><td>领域知识提升为方案创建流程 Step 2,并作为 AI 补全的优先参考资料:① 方案向导步骤重排——领域知识由末段移至「方案基本属性」之后(Step 2),原 资产设置 / 功能与菜单 / 运维流程 / 生成保存 / 专家确认 整体后移一位(<code>DOMAIN_STEPS</code> 顺序调整,key 不变,步进导航自动适配)。② 新增「领域知识」步骤(<code>KnowledgeStep</code>):支持「上传领域知识文档」(多文件,base64 经既有 <code>POST /api/domain-templates/:id/docs</code> 落库,文本类读正文、其他格式登记为参考)与「配置参考 URL 站点」(<code>doc_type=link</code>),列表展示名称/类型/大小/时间并提供「下载」(仅文档)与「删除」(<code>DELETE /api/knowledge-docs/:id</code>,同步清理磁盘文件);知识随方案持久化、可删。③ 知识驱动 AI 补全:新增 <code>buildKnowledgePrompt(ctx)</code>(镜像 <code>buildStylePrompt</code>),读取本方案 <code>knowledge_docs</code>——文本文档直接注入正文、URL 站点由服务端 <code>fetch</code> 抓取正文注入(8s 超时,失败降级为仅保留 URL 链接作为参考),注入到 <code>buildCompletePrompt</code> / <code>buildCompleteAssetsPrompt</code> / <code>buildCompleteCubesPrompt</code> 三处(三个构建器改为 async、8 处调用点加 await;领域级补全 ctx 增加 <code>id</code>,站点级无 id 不注入)。优先级为「优先依据已上传领域知识分析补全,再做大模型在线补充」。④ 生成保存(<code>DomainGenerateReview</code>)「领域知识」行由「(待开发)」改为真实文档信息(份数 + 名称列举),并提供「返回领域知识」快捷入口;运维流程 AI 补全本期仍占位(<span class="todo">尚未实现</span>)。</td></tr>
     <tr><td>v0.21</td><td>2026-08-20</td><td>wang.yu</td><td>领域方案库 Cube 扩展页面支持选择页面样式库样式后生成(镜像站点侧 <code>sites.style_id</code> 机制,方案级):① 后端 <code>domain_templates</code> 新增 <code>style_id</code> 列(<code>ensureColumn</code> 增量加列);<code>handleDomainTemplateUpdate</code> 支持写入/显式置空;<code>hydrateDomainTemplateCtx</code> 返回真实 <code>style_id</code>(此前硬编码 null)。② 前端领域方案详情「功能与菜单 → Cube 扩展页面」顶部新增「领域方案页面样式」选择器(复用 <code>.style-picker</code> 与页面样式库缩略图),选中即 PUT <code>/domain-templates/:id</code> 持久化;生成/AI 修改经共享的 <code>buildStylePrompt</code> 自动注入所选样式的 JSON 主题,无需改动提示词构造。</td></tr>
     <tr><td>v0.20</td><td>2026-08-20</td><td>wang.yu</td><td>领域分类管理增强:① 默认分类调整——移除「工业制造 / 工业园区 / 智慧楼宇」,新增「其他」(<code>DEFAULT_DOMAINS</code> 同步更新,重启不再复活被删分类);② 领域方案库新增「管理领域」面板,列出全部领域及其方案数,「其他」标注「默认保留」且不可删除,其余领域提供「删除」按钮,经自定义确认弹窗二次确认后调用 <code>DELETE /api/domains/:id</code>;③ 后端新增 <code>DELETE /api/domains/:id</code>:禁止删除「其他」,删除前将待删领域的 <code>domain_templates</code> 与 <code>device_template_domains</code> 关联迁移至「其他」(避免 <code>ON DELETE CASCADE</code> 误删方案),再删除领域;④ 删除后前端刷新领域列表,并在当前筛选命中该领域时自动清除筛选。</td></tr>