Ryuiso 3 тижнів тому
батько
коміт
20c40d9e99

+ 179 - 1
package-lock.json

@@ -22,7 +22,8 @@
         "react-markdown": "^10.1.0",
         "react-markdown-editor-lite": "^1.3.4",
         "react-quill": "^2.0.0",
-        "react-router-dom": "^7.1.0"
+        "react-router-dom": "^7.1.0",
+        "rehype-raw": "^7.0.0"
       },
       "devDependencies": {
         "@types/markdown-it": "^14.1.2",
@@ -3121,6 +3122,64 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/hast-util-from-parse5": {
+      "version": "8.0.3",
+      "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz",
+      "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "@types/unist": "^3.0.0",
+        "devlop": "^1.0.0",
+        "hastscript": "^9.0.0",
+        "property-information": "^7.0.0",
+        "vfile": "^6.0.0",
+        "vfile-location": "^5.0.0",
+        "web-namespaces": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/hast-util-parse-selector": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+      "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/hast-util-raw": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
+      "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "@types/unist": "^3.0.0",
+        "@ungap/structured-clone": "^1.0.0",
+        "hast-util-from-parse5": "^8.0.0",
+        "hast-util-to-parse5": "^8.0.0",
+        "html-void-elements": "^3.0.0",
+        "mdast-util-to-hast": "^13.0.0",
+        "parse5": "^7.0.0",
+        "unist-util-position": "^5.0.0",
+        "unist-util-visit": "^5.0.0",
+        "vfile": "^6.0.0",
+        "web-namespaces": "^2.0.0",
+        "zwitch": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/hast-util-to-jsx-runtime": {
       "version": "2.3.6",
       "resolved": "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
@@ -3148,6 +3207,35 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/hast-util-to-parse5": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
+      "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "comma-separated-tokens": "^2.0.0",
+        "devlop": "^1.0.0",
+        "property-information": "^6.0.0",
+        "space-separated-tokens": "^2.0.0",
+        "web-namespaces": "^2.0.0",
+        "zwitch": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
+    "node_modules/hast-util-to-parse5/node_modules/property-information": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+      "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/hast-util-whitespace": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
@@ -3161,6 +3249,23 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/hastscript": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz",
+      "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "comma-separated-tokens": "^2.0.0",
+        "hast-util-parse-selector": "^4.0.0",
+        "property-information": "^7.0.0",
+        "space-separated-tokens": "^2.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/he": {
       "version": "1.2.0",
       "resolved": "https://registry.npmmirror.com/he/-/he-1.2.0.tgz",
@@ -3195,6 +3300,16 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/html-void-elements": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+      "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/iconv-lite": {
       "version": "0.6.3",
       "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -4498,6 +4613,30 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/parse5": {
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "license": "MIT",
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
+    },
+    "node_modules/parse5/node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/path-browserify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -5444,6 +5583,21 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/rehype-raw": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
+      "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/hast": "^3.0.0",
+        "hast-util-raw": "^9.0.0",
+        "vfile": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/remark-parse": {
       "version": "11.0.0",
       "resolved": "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -6048,6 +6202,20 @@
         "url": "https://opencollective.com/unified"
       }
     },
+    "node_modules/vfile-location": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+      "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/unist": "^3.0.0",
+        "vfile": "^6.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/unified"
+      }
+    },
     "node_modules/vfile-message": {
       "version": "4.0.3",
       "resolved": "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz",
@@ -6169,6 +6337,16 @@
       "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
       "license": "MIT"
     },
+    "node_modules/web-namespaces": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+      "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+      "license": "MIT",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/wooorm"
+      }
+    },
     "node_modules/yallist": {
       "version": "3.1.1",
       "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",

+ 2 - 1
package.json

@@ -25,7 +25,8 @@
     "react-markdown": "^10.1.0",
     "react-markdown-editor-lite": "^1.3.4",
     "react-quill": "^2.0.0",
-    "react-router-dom": "^7.1.0"
+    "react-router-dom": "^7.1.0",
+    "rehype-raw": "^7.0.0"
   },
   "devDependencies": {
     "@types/markdown-it": "^14.1.2",

+ 100 - 0
src/help/components/HelpLayout.tsx

@@ -0,0 +1,100 @@
+import React, { useEffect, useMemo, useState } from 'react';
+import { Layout, Menu } from 'antd';
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
+import { helpMenu, HelpMenuItem } from '../menu';
+import MarkdownViewer, { TocItem } from './MarkdownViewer';
+
+const { Sider, Content } = Layout;
+
+// 动态加载 md 文件(Vite 6 写法)
+const files = import.meta.glob('/src/help/docs/**/*.md', { query: '?raw', import: 'default' });
+
+function findMenuByPath(pathname: string): HelpMenuItem | undefined {
+  const flat: HelpMenuItem[] = [];
+  const dfs = (arr: HelpMenuItem[]) => arr.forEach((i) => { flat.push(i); i.children && dfs(i.children); });
+  dfs(helpMenu);
+  return flat.find((i) => i.path.split('#')[0] === pathname);
+}
+
+const HelpLayout: React.FC = () => {
+  const params = useParams();
+  const navigate = useNavigate();
+  const location = useLocation();
+  const pathname = location.pathname;
+
+  const [md, setMd] = useState('');
+  const [toc, setToc] = useState<TocItem[]>([]);
+
+  const current = useMemo(() => findMenuByPath(pathname), [pathname]);
+
+  useEffect(() => {
+    // 根据菜单项的 doc 加载 md
+    const doc = current?.doc;
+    if (!doc) { setMd('# 欢迎使用帮助中心'); return; }
+    const key = `/src/help/docs/${doc}`;
+    const loader = (files as any)[key];
+    if (loader) {
+      (loader as () => Promise<string> )().then((raw) => setMd(raw));
+    } else {
+      setMd('> 文档未找到: ' + doc);
+    }
+  }, [current?.doc]);
+
+  useEffect(() => {
+    // 定位到 hash 标题
+    if (!location.hash) return;
+    const id = decodeURIComponent(location.hash.slice(1));
+    const el = document.getElementById(id);
+    if (el) {
+      el.scrollIntoView({ behavior: 'smooth', block: 'start' });
+    }
+  }, [md, location.hash]);
+
+  // build antd menu items
+  const menuItems = useMemo(() => {
+    const toAntd = (items: HelpMenuItem[]): any[] => items.map((it) => ({
+      key: it.path,
+      label: it.title,
+      children: it.children ? toAntd(it.children) : undefined,
+    }));
+    return toAntd(helpMenu);
+  }, []);
+
+  const defaultOpen = useMemo(() => pathname.split('/').slice(0, 3).join('/'), [pathname]);
+
+  return (
+    <Layout style={{ height: '100%', background: 'transparent' }}>
+      <Sider width={240} style={{ background: '#fff', borderRight: '1px solid #f0f0f0' }}>
+        <div style={{ padding: 12, fontWeight: 600 }}>帮助文档</div>
+        <Menu
+          mode="inline"
+          selectedKeys={[pathname + (location.hash || '')]}
+          defaultOpenKeys={[defaultOpen]}
+          items={menuItems}
+          onClick={(e) => navigate(e.key)}
+        />
+      </Sider>
+      <Content style={{ padding: 16, overflow: 'auto' }}>
+        <div style={{ display: 'flex', gap: 24 }}>
+          <div style={{ flex: 1, minWidth: 0 }}>
+            <MarkdownViewer content={md} onToc={setToc} />
+          </div>
+          <div style={{ width: 220 }}>
+            <div style={{ position: 'sticky', top: 16 }}>
+              <div style={{ marginBottom: 8, fontWeight: 600 }}>On this page</div>
+              <div>
+                {toc.map((t) => (
+                  <div key={t.id} style={{ marginLeft: (t.level - 1) * 12 }}>
+                    <a href={`#${t.id}`}>{t.text}</a>
+                  </div>
+                ))}
+              </div>
+            </div>
+          </div>
+        </div>
+      </Content>
+    </Layout>
+  );
+};
+
+export default HelpLayout;

+ 91 - 0
src/help/components/MarkdownViewer.tsx

@@ -0,0 +1,91 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import ReactMarkdown from 'react-markdown';
+import rehypeRaw from 'rehype-raw';
+
+export type TocItem = { id: string; text: string; level: number };
+
+function slugify(text: string) {
+  return text
+    .trim()
+    .replace(/\s+/g, '-')
+    .replace(/[()():/??、,.。!!'"`]/g, '')
+    .toLowerCase();
+}
+
+interface MarkdownViewerProps {
+  content: string;
+  onToc?: (toc: TocItem[]) => void;
+}
+
+const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ content, onToc }) => {
+  const [toc, setToc] = useState<TocItem[]>([]);
+  const containerRef = useRef<HTMLDivElement | null>(null);
+
+  const components = useMemo(() => {
+    const getNodeText = (node: any): string => {
+      if (node == null) return '';
+      if (typeof node === 'string' || typeof node === 'number') return String(node);
+      if (Array.isArray(node)) return node.map(getNodeText).join('');
+      if (node.props && node.props.children) return getNodeText(node.props.children);
+      return '';
+    };
+    const Heading = (level: number) =>
+      function H(props: any) {
+        const childrenText = getNodeText(props.children);
+        const id = slugify(childrenText);
+        return React.createElement(
+          'h' + level,
+          { id },
+          props.children
+        );
+      };
+
+    return {
+      h1: Heading(1),
+      h2: Heading(2),
+      h3: Heading(3),
+      table: (props: any) => (
+        <div style={{ overflowX: 'auto' }}>
+          <table {...props} />
+        </div>
+      ),
+      code: (props: any) => (
+        <code style={{ background: '#f6f7f9', padding: '2px 4px', borderRadius: 4 }} {...props} />
+      ),
+      a: (props: any) => <a target="_blank" rel="noreferrer" {...props} />,
+      img: (props: any) => (
+        <img
+          style={{ maxWidth: '100%', height: 'auto', display: 'block', margin: '12px auto' }}
+          {...props}
+        />
+      ),
+    } as any;
+  }, []);
+
+  useEffect(() => {
+    // build toc after render
+    const root = containerRef.current;
+    if (!root) return;
+    const nodes = Array.from(root.querySelectorAll('h1, h2, h3')) as HTMLElement[];
+    const items = nodes.map((n) => ({ id: n.id, text: n.textContent || '', level: Number(n.tagName.substring(1)) }));
+    setToc(items);
+    onToc?.(items);
+  }, [content, onToc]);
+
+  return (
+    <div ref={containerRef} className="help-markdown">
+      {/* Ensure raw HTML elements styled as documentation site */}
+      <style>{`
+        .help-markdown img{max-width:100%;height:auto;display:block;margin:12px auto;}
+        .help-markdown figure{margin:16px auto;max-width:100%;}
+        .help-markdown figure img{display:block;margin:0 auto;}
+        .help-markdown figcaption{margin-top:8px;color:#666;font-size:12px;text-align:center;}
+      `}</style>
+      <ReactMarkdown rehypePlugins={[rehypeRaw]} components={components}>
+        {content}
+      </ReactMarkdown>
+    </div>
+  );
+};
+
+export default MarkdownViewer;

+ 23 - 0
src/help/docs/audit/intro.md

@@ -0,0 +1,23 @@
+---
+title: 应用审核
+order: 30
+---
+
+# 应用审核
+
+用于管控应用上线前的质量与合规。根据平台策略,可配置多人审核、意见记录与状态追踪。
+
+## 快速上手
+1. 在“应用广场”完成应用配置与自测
+2. 提交审核,填写必要说明
+3. 审核人在线查看应用信息与调试记录,给出结论
+4. 审核通过后应用可对外发布
+
+## 审核要点
+- 基于企业知识,回答是否准确且可追溯到来源
+- 是否包含敏感信息或越权调用
+- 日志与数据留存是否符合规范
+
+## 常见问题
+- 审核未通过:根据意见修改应用参数或知识库内容后重新提交
+- 审核人不可见:检查权限配置或联系管理员

+ 22 - 0
src/help/docs/dataExport/intro.md

@@ -0,0 +1,22 @@
+---
+title: 数据导出
+order: 20
+---
+
+# 数据导出
+
+用于导出系统运行数据(对话、应用、知识库等),支持按时间范围与条件筛选导出。
+
+## 快速上手
+1. 进入“数据导出”菜单
+2. 选择导出类型(对话/应用/知识库等)
+3. 设置时间范围与筛选条件
+4. 点击导出并等待生成文件
+
+## 建议
+- 大范围导出可能耗时较久,建议分段导出
+- 若需定制字段/模板,请联系管理员配置
+
+## 常见问题
+- 无法下载:检查浏览器拦截/登录状态
+- 字段缺失:切换导出模板或联系管理员扩展

+ 47 - 0
src/help/docs/guide/overview.md

@@ -0,0 +1,47 @@
+---
+title: 概述
+order: 1
+---
+
+# 平台总览
+
+本系统面向 AI 应用搭建与知识管理,提供从知识入库、问答调用、到数据导出与审核的全链路能力。你可以在左侧菜单快速进入各模块,或通过右侧目录定位到本页任意小节。
+
+## 功能构成
+
+- 应用广场(问答应用)
+  - 创建/修改应用,配置模型、知识库、检索参数等
+  - 在线调试(对话预览)、记录管理
+- 知识库
+  - 文档上传(多格式)、切片处理、向量化
+  - 文档与切片的查看、编辑与联动
+- 数据导出
+  - 对话、应用、知识库相关报表导出
+- 应用审核(可选)
+  - 应用上线前的流程化审核
+
+## 快速上手
+
+1. 在“知识库”中创建知识库,上传文档并完成切片与向量化
+2. 在“应用广场”创建问答应用,选择要关联的知识库并保存
+3. 在应用详情页进行在线调试,验证检索与回答效果
+4. 如需对外使用,提交“应用审核”(如开启该流程)
+5. 使用“数据导出”导出运行数据
+
+## 核心概念
+
+- 知识库:用于存放企业/项目文档的容器,文档会被解析为“切片”并向量化
+- 切片:从原始文档中拆分出的最小检索单元,便于高效召回
+- 问答应用:对接模型与知识库的应用实例,负责接收问题、检索知识、生成回答
+- 向量化:将文本转换为向量表示的过程,用于相似度检索
+
+## 常见问题(FAQ)
+
+### 文档上传后未出现切片?
+检查文件类型是否支持、队列是否完成处理,或在详情页手动触发处理。
+
+### 应用回答与知识不一致?
+在应用参数里调小“生成长度”,增大“检索条数/相关性阈值”,并确认已关联正确的知识库。
+
+### 导出中缺少我需要的字段?
+到“数据导出”中选择对应报表模板;如仍缺失,请联系管理员扩展导出字段。

+ 34 - 0
src/help/docs/knowledge/intro.md

@@ -0,0 +1,34 @@
+---
+title: 知识库介绍
+order: 10
+---
+
+# 知识库
+
+知识库用于统一管理需要参与问答检索的企业文档,并将其切片、向量化后供应用调用。
+
+## 功能清单
+- 新建/编辑/删除知识库
+- 文档管理:上传、替换、删除、预览
+- 切片管理:查看、编辑、定位到原文
+- 向量化与状态监控
+
+## 支持的文件类型
+- 常见 Office:docx、xlsx、pptx
+- PDF、txt、md、csv
+- 图片 OCR(如开启)
+
+## 处理流程
+1. 新建知识库,设置名称与描述
+2. 上传文档,等待解析完成
+3. 进入文档详情查看“切片信息”,可手动编辑切片文本
+4. 触发/等待向量化完成,状态为“已向量化”即可用于检索
+
+## 最佳实践
+- 文档尽量结构化、标题清晰,有利于生成高质量切片
+- 对长文档酌情拆分章节上传;减少无意义页眉页脚
+- 关键表格建议转为文本或 CSV,便于检索
+
+## 常见问题
+- 文档解析失败:检查文件是否加密、体积是否过大;必要时转为 PDF 再上传
+- 检索不到内容:确认向量化完成;调整应用的 TopK 与相似度阈值

+ 25 - 0
src/help/docs/questionAnswer/intro.md

@@ -0,0 +1,25 @@
+---
+title: 应用广场(问答应用)
+order: 1
+---
+
+# 问答应用
+
+## 适用场景
+- 面向内部/外部用户提供基于企业知识的问答能力
+- 与知识库联动,支持私有化知识检索
+
+## 快速上手
+1. 新建应用,填写名称与描述
+2. 选择模型与参数(温度、最大生成长度等)
+3. 关联知识库与检索参数(TopK、相似度阈值)
+4. 保存后进入应用详情页,使用“在线调试”验证效果
+
+## 关键配置
+- 模型与温度:影响生成的稳定性与创造性
+- 检索参数:决定召回片段数量与质量
+- 知识库绑定:支持绑定多个知识库(若平台开启)
+
+## 常见问题
+- 回答不命中:调大 TopK/降低阈值,或优化文档切片粒度
+- 回答冗长:缩短“最大生成长度”,或在提示词中限制字数

+ 47 - 0
src/help/menu.ts

@@ -0,0 +1,47 @@
+export type HelpMenuItem = {
+  key: string;
+  title: string;
+  path: string; // e.g. /help/guide/overview or /help/knowledge/intro#快速上手
+  doc?: string; // relative to src/help/docs
+  children?: HelpMenuItem[];
+};
+
+export const helpMenu: HelpMenuItem[] = [
+  {
+    key: 'overview',
+    title: '概述',
+    path: '/help/guide/overview',
+    doc: 'guide/overview.md',
+  },
+  {
+    key: 'qa',
+    title: '应用广场(问答应用)',
+    path: '/help/questionAnswer/intro',
+    doc: 'questionAnswer/intro.md',
+  },
+  {
+    key: 'knowledge',
+    title: '知识库',
+    path: '/help/knowledge/intro',
+    doc: 'knowledge/intro.md',
+    children: [
+      {
+        key: 'quickstart',
+        title: '快速上手',
+        path: '/help/knowledge/intro#快速上手',
+      },
+    ],
+  },
+  {
+    key: 'dataExport',
+    title: '数据导出',
+    path: '/help/dataExport/intro',
+    doc: 'dataExport/intro.md',
+  },
+  {
+    key: 'audit',
+    title: '应用审核',
+    path: '/help/audit/intro',
+    doc: 'audit/intro.md',
+  },
+];

+ 17 - 0
src/pages/layout/components/Nav.tsx

@@ -76,6 +76,14 @@ const Nav : React.FC<Props> = ( props : Props ) => {
         router.navigate( { pathname: '/deepseek/audit' } )
       }
     },
+    {
+      key: '/help/guide/overview',
+      icon: <ReadOutlined />,
+      label: '帮助文档',
+      onClick: () => {
+        window.open('/help/guide/overview', '_blank')
+      }
+    },
   ].filter( item => {
     if ( item.key === '/deepseek/questionAnswer' ) {
       return LocalStorage.getStatusFlag( 'deepseek:application:list' );
@@ -117,6 +125,15 @@ const Nav : React.FC<Props> = ( props : Props ) => {
         router.navigate( { pathname: '/zhipu/dataExport' } )
       }
     }
+    ,
+    {
+      key: '/help/guide/overview',
+      icon: <ReadOutlined />,
+      label: '帮助文档',
+      onClick: () => {
+        router.navigate( { pathname: '/help/guide/overview' } )
+      }
+    }
   ];
   
   

+ 17 - 15
src/pages/layout/index.tsx

@@ -33,6 +33,7 @@ const LayoutApp: React.FC = () => {
 
   const matches = useMatches();
   const location = useLocation();
+  const isHelp = location.pathname.startsWith('/help');
 
   React.useEffect(() => {
     const list = matches.filter((item: any) => item.handle?.menuLevel && item.handle?.breadcrumbName).map((item: any) => {
@@ -97,30 +98,31 @@ const LayoutApp: React.FC = () => {
         currentMenuType={menuType}
       />
       <div style={{ display: 'flex', marginTop: '64px', backgroundColor: '#fff' }}>
-        <Nav
-          selectedKey={selectedKey}
-          onChangeSelectedKey={onChangeSelectedKey}
-          openKeys={openKeys}
-          onOpenChange={onOpenChange}
-          collapsed={collapsed}
-          onClickCollapsed={onClickCollapsed}
-          menuType={menuType}
-        />
+        { !isHelp && (
+          <Nav
+            selectedKey={selectedKey}
+            onChangeSelectedKey={onChangeSelectedKey}
+            openKeys={openKeys}
+            onOpenChange={onOpenChange}
+            collapsed={collapsed}
+            onClickCollapsed={onClickCollapsed}
+            menuType={menuType}
+          />
+        ) }
         <Layout style={{
-          marginLeft: collapsed ? '80px' : '200px',
-          width: collapsed ? 'calc(100% - 80px)' : 'calc(100% - 200px)',
+          marginLeft: isHelp ? '0' : (collapsed ? '80px' : '200px'),
+          width: isHelp ? '100%' : (collapsed ? 'calc(100% - 80px)' : 'calc(100% - 200px)'),
           backgroundColor: '#fff',
           minHeight: 'calc(100vh - 64px)',
           transition: 'all 0.2s ease'
         }}>
 
           <Content className='main-content'>
-            {
+            {(!isHelp) && (
               location.pathname === '/404' ?
-                <div style={{ width: '100%', height: 20 }}></div>
-                :
+                <div style={{ width: '100%', height: 20 }}></div> :
                 <Breadcrumb routerMatchList={routerMatchList} />
-            }
+            )}
             <div className='content-scroll-area'>
               <Outlet />
             </div>

+ 8 - 0
src/router.tsx

@@ -132,6 +132,14 @@ const routerList: RouteObject[] = [
                 },
                 element: lazyLoad(() => import('@/pages/deepseek/audit/index')),
             },
+            {   /* 帮助文档 */
+                path: '/help/*',
+                handle: {
+                    menuLevel: 1,
+                    breadcrumbName: '帮助文档',
+                },
+                element: lazyLoad(() => import('@/help/components/HelpLayout')),
+            },
             {   /* 404 */
                 path: '/404',
                 element: lazyLoad(() => import('@/components/404/index')),