typedoc助力组件AI知识库文档搭建

260 阅读5分钟

一、前言

当前AI、大模型、知识库等词条随处可见,随处翻翻就是一大片的大模型私有化部署+私有知识库相关帖子。

但个人认为对于个人来说,完全没有大模型私有化部署的需求,而且费了许多资源部署落地的可能也只是一个鸡肋;对于知识库而言,首先个人知识很难说有没有安全隐私的保护需求,另外又有多少人能够写出大量的篇幅文档需要借助ai搜索而常规的检索(如分目录+ctrl + f)不可靠呢。就目前观察一些文档管理工具对知识的管理已经足够满足个人的知识存储了。

不过对于企业、部门或者软件开发者,知识库还是存在一定的价值,一方面知识体量可能非常巨大,常规检索效率低下;另一方面知识查询者对知识可能不了解,常规检索无从下手或者找不到知识在何处。

大模型+知识库最重要的个人认为是知识,没有知识的知识库是空中楼阁,一定是先有的知识,才有的ai知识库的需求。因此知识的积累是这里的关键,对于复杂业务、复杂知识,可能手动编写知识文档更为合适,而如果是一些组件库、sdkapi等,知识的重点在于输入输出、属性、方法等等,完全可以通过代码注释生成的文档满足知识的积累需要,本文将提供一种思路。

二、typedoc生成文档

几乎每一种语言都有相应的工具能够生成项目文档,这里使用基于typescripttypedoc工具,原先已经有大量官方静态站点可能已经由它生成,进一步生成知识文档可以说是0成本,typedoc+typedoc-plugin-markdown就能够生成markdown文档。

对于以下目录结构的组件库,每一个组件的propsemitsslots等定义在了props.ts文件中,组件使用者一般也只需要关注着一些信息。

image.png

input/src/props.ts

/** @module Input */
import { InferVueDefaults } from '@/shared';
import type Input from './input.vue';

/**
 * 输入框组件的属性如下
*/
export interface InputProps {
  /**
   * 输入值,支持 v-model 双向绑定
   * @default ''
   */
  modelValue?: string | number | null | undefined;
  /**
   * 类型,与原生input一致
   * @default 'text'
   */
  type?: string;
  /**
   * 是否禁用
   * @default false
   */
  disabled?: boolean;
  /**
   * 是否只读,只读时一定为禁用
   * @default false
   */
  readonly?: boolean;
  /**
   * 最大输入长度
   * @default ''
   */
  maxlength?: string | number
  /**
   * 最小输入长度
   * @default ''
   */
  minlength?: string | number
  /**
   * 统计数值
   * @default false
   */
  showCount?: boolean
  /**
   * 占位文本
   * @default ''
   */
  placeholder?: string
  /**
   * 是否可清除
   * @default false
   */
  clearable?: boolean
  /**
   * 是否显示密码显隐按钮
   * @default false
   */
  showPassword?: false
  /**
   * 尺寸
   * @default 'medium'
   */
  size?: string
  /**
   * 前缀图标
   * @default ''
   */
  prefixIcon?: string
  /**
   * 前缀图标是否不响应事件
   * @default: true
   */
  prefixIconSilent?: boolean
  /**
   * 后缀图标
   * @default ''
   */
  suffixIcon?: string
  /**
   * 后缀图标是否不响应事件
   * @default: true
   */
  suffixIconSilent?: boolean
  /**
   * 后缀内容,如单位等
   * @default ''
   */
  suffixText?: string
  /**
   * 是否自动补全
   * @default ''
   */
  autocomplete?: string
  /**
   * 原生属性
   * @default ''
   */
  name?: string
  /**
   * 原生属性
   * @default false
   */
  autofocus?: boolean
  /**
   * 原生属性
   */
  form?: string
  /**
   * 原生aria-label属性
   */
  label?: string
  /**
   * 原生属性
   */
  tabindex?: string | number
  /**
   * 是否触发表单验证
   * @default true
   */
  validateEvent?: boolean
  /**
   * 原生属性
   */
  max?: string
  /**
   * 原生属性
   */
  min?: string
  /**
   * 原生属性
   */
  step?: string
}

/** @hidden */
export function defaultInputProps(): Required<InferVueDefaults<InputProps>> {
  return {
    modelValue: '',
    type: 'text',
    disabled: false,
    readonly: false,
    maxlength: '',
    minlength: '',
    showCount: false,
    placeholder: '',
    clearable: false,
    showPassword: false,
    size: 'medium',
    prefixIcon: '',
    prefixIconSilent: true,
    suffixIcon: '',
    suffixIconSilent: true,
    suffixText: '',
    autocomplete: '',
    name: '',
    autofocus: false,
    form: '',
    label: '',
    tabindex: '',
    validateEvent: true,
    min: '',
    max: '',
    step: '',
  };
}

/**
 * 输入框组件的事件
 * @interface
 * */
export type InputEmits = {
  'update:modelValue': [value: string];
  /**
   * input事件
   * @param value 输入值
   */
  input: [value: string];
  /**
   * change事件
   * @param value 输入值
   */
  change: [value: string];
  /**
   * 输入框失去焦点
   * @param e 事件对象
   */
  focus: [e: FocusEvent];
  /**
   * 输入框获取焦点
   * @param e 事件对象
   */
  blur: [e: FocusEvent];
  /**
   * 输入框键盘按下
   * @param e 事件对象
   */
  keydown: [e: KeyboardEvent];
  /**
   * 前缀图标点击事件
   * @param e 事件对象
   */
  prefixIconClick: [e: MouseEvent];
  /**
   * 后缀图标点击事件
   * @param e 事件对象
   */
  suffixIconClick: [e: MouseEvent];
};

/** 输入框组件对外暴露的方法 */
export interface InputExpose {
  /**
   * 清空输入框
   * @returns void
  */
  clear: () => void
  /**
   * 聚焦输入框
   * @returns void
   */
  focus: () => void
}

/** 按钮组件的插槽信息 */
export interface InputSlots {
  /**
   * 前缀插槽,使用:v-slot:prefix
   */
  prefix: any;
  /**
   * 后缀插槽,使用:v-slot:suffix
   */
  suffix: any;
}

/** @hidden */
export type InputInstance = InstanceType<typeof Input>;

下面是typedoc简单生成markdown的配置:

import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { readFile, writeFile } from "node:fs/promises";
import {
  Application,
  TSConfigReader,
  ReflectionKind
} from "typedoc";
import defaultLocaleJson from "../locale/default.json" assert { type: "json" };
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const fromRoot = (...paths) => join(
  __dirname,
  "..",
  "..",
  ...paths
);
const tsConfigPath = fromRoot("tsconfig.src.json");
const OUT_DIR = join(__dirname, "..", "api");
const CONFIGS_DIR = join(__dirname, "..", "configs");
async function main() {
  const app = await Application.bootstrapWithPlugins({
    entryPoints: [fromRoot("packages", "**", "props.ts").replace(/\\/g, "/")],
    tsconfig: tsConfigPath,
    // 启用 markdown 转化插件
    plugin: ["typedoc-plugin-markdown"],
    disableSources: true,
    readme: "none",
    skipErrorChecking: true,
    // 这里配置了因此页首、面包屑等
    hidePageHeader: true,
    hideBreadcrumbs: true,
    useCodeBlocks: true,
    // 这一步转换标题 如 InputProps ——> 输入框属性
    pageTitleTemplates: {
      member: (args) => `${splitByUpperCase(args.name).map((item) => defaultLocaleJson[item]).join("")}`
    },
    interfacePropertiesFormat: "table",
    out: OUT_DIR,
    lang: "zh"
  }, [
    new TSConfigReader()
  ]);
  const project = await app.convert();
  if (project) {
    await app.generateDocs(project, OUT_DIR);
    await app.generateOutputs(project);
    const jsonDir = join(OUT_DIR, "documentation.json");
    await app.generateJson(project, jsonDir);
  }
}
main().catch(console.error);

需要注意的是不能够使用 tsx 执行这一脚本,因为typedoc自身模块导入的问题会使得插件失效,建议直接使用nodejs运行.mjs脚本

生成效果如下:

image.png

3.导入知识库与工作流

这里使用的是dify,导入过程非常简单不赘述。实践中由于各文件体积较小且基本一个文件就是一块内容,因此chunk的分割按文件分割效果较好。

image.png

下面需要搭建一条问答的工作流,如下:

image.png

当能够从知识库中检索到相关知识时,就会结合大模型输出相关知识,否则会告知未检索到相关内容。

4.效果

1.提问输入框支持哪些属性

image.png

2.提问如何设置输入框禁用

image.png

3.提问如何给输入框设置前缀图标

image.png

可以看到效果还是比较不错的,不过后面的代码示例中出现了el-element的标签,这一部分应该可以通过提示词的调整来优化,但不影响总体的可行性判定。