RAG笔记(2)ragflow-web二开
记录一下 ragflow-web 二开的过程。
基础知识
- Umi 以路由为基础,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。
- 总结一下 Outlet 的主要特点:
路由嵌套:
- 允许在父路由组件中渲染子路由组件
- 实现路由的层级结构
布局复用: - 可以在多个页面之间共享相同的布局
- 只需要改变 Outlet 位置的内容
权限控制: - 可以在渲染子路由之前进行权限验证
- 根据条件决定是否渲染子路由内容
组件复用: - 避免重复编写相同的布局代码
- 提高代码的可维护性
这是 UmiJS 框架提供的一个非常实用的功能,特别适合构建具有复杂路由结构的应用程序。
前端架构
- 前端使用 react + umi 框架,经多次实验,已定版本 umi4.4.4;
- TypeScript 作为开发语言,Tailwind CSS 作为样式解决方案;
- 组件使用 Ant Design,可作定制化修改;
- ESLint 和 Prettier 用于代码规范,Jest 用于单元测试;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31├── docs/ # 文档,上级目录中 copy 过来,保证项目正常运行
├── dist/ # npm run build 之后打包的文件
├── public/ # 静态资源目录
├── package.json # 项目依赖和脚本配置
├── tsconfig.json # typeScript 配置文件
├── tailwind.config.js 和 tailwind.css # Tailwind CSS 配置和样式文件
├── .umirc.ts # UmiJS 框架的配置文件
├── .env # 运行时环境配置
├── typings.d.ts # 全局变量/模块声明
├── docs/ # 文档
├── src/
│ ├── .umi/ # dev 时的临时文件目录,比如入口文件、路由等,都会被临时生成到这里
│ ├── .umi-production/ # build 时的临时文件目录,比如入口文件、路由等,都会被临时生成到这里
│ ├── pages/ # 页面组件目录
│ │ ├── agent/ # Agent 流程图 UI(Canvas + DSL 构建)
│ │ ├── chat/ # 聊天界面:配置、消息流、Prompt 管理
│ │ ├── dataset/ # 数据集:文档上传、解析、标签设置
│ │ ├── add-knowledge/ # 添加知识:文档→结构化→知识图谱
│ │ ├── flow/ # Flow 模式 UI,与 Agent 机制相似
│ └── layouts/ # 布局组件目录
│ │ ├── index.tsx # umi会根据路由进行匹配,匹配到的组件都会放到Outlet中渲染
│ ├── components/ # 通用组件库:表单、Modal、预览器等
│ ├── hooks/ # 与后端服务配合的数据处理逻辑封装
│ └── utils/ # 工具函数
│ └── assets/ # 静态资源文件,img
│ └── locales/ # 国际化文件,当前只保留中文
│ └── theme/ # 主题相关文件
│ └── wrapper/ # 权限包裹组件(临时注释可避开验证)
│ └── less/ # 全局样式文件
│ └── app.tsx # 应用入口文件
│ └── routes.ts # 路由配置文件
前端代码修改思路
框架修改
.umirc.ts
作为基础框架,可设置系统配置、全局路由、接口代理、插件。- 最终路由导入到 umi 配置文件中。若需要修改,在
routers.ts
定义路由,pages
文件夹下新建页面组件;(umi支持约定式路由和配置式路由,这里选择配置式) - 若需要修改后端接口代理,添加
proxy
; - 若需要在入口文件
index.html
(运行时umi自动生成)中添加静态文件,可在public
中引入,然后在headScripts
中添加文件名; - 配置修改:
- Logo: 直接修改
public/logo.svg
- 标题:
conf.json
中修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58import path from 'path';
import TerserPlugin from 'terser-webpack-plugin';
import { defineConfig } from 'umi';
import { appName } from './src/conf.json';
import routes from './src/routes';
export default defineConfig({
title: appName,
outputPath: 'dist',
alias: { '@parent': path.resolve(__dirname, './') },
npmClient: 'npm',
base: '/',
routes,
publicPath: '/',
esbuildMinifyIIFE: true,
icons: {},
hash: true,
favicons: ['/logo.svg'],
clickToComponent: {},
history: {
type: 'browser',
},
headScripts: [
{ src: '/config.js' }
],
plugins: [
'@react-dev-inspector/umi4-plugin',
'@umijs/plugins/dist/tailwindcss',
],
jsMinifier: 'none',
lessLoader: {
modifyVars: {
hack: `true; @import "~@/less/index.less";`,
},
},
devtool: 'source-map',
copy: [
{ from: 'src/conf.json', to: 'dist/conf.json' },
{ from: 'node_modules/monaco-editor/min/vs/', to: 'dist/vs/' },
],
proxy: [
{
context: ['/api', '/v1'],
target: 'http://127.0.0.1:9380/',
changeOrigin: true,
ws: true,
logger: console,
// pathRewrite: { '^/v1': '/v1' },
},
],
chainWebpack(memo, args) {
memo.module.rule('markdown').test(/\.md$/).type('asset/source');
memo.optimization.minimizer('terser').use(TerserPlugin); // Fixed the issue that the page displayed an error after packaging lexical with terser
return memo;
},
tailwindcss: {},
});
样式修改
- 若需要改变前端布局,修改
layouts
文件夹下的内容;header
为头部导航,right-toolbar
暂时不用; app.tsx
中可修改 ant-design 主题相关或 token 级别组件相关。字体使用 inter,在assets/inter
中可见倒入的字体。1
2
3
4
5
6
7
8
9
10<ConfigProvider
theme={{
token: {
fontFamily: 'Inter',
colorPrimary: '#165DFF',
},
}}
locale={locale}
>
<SidebarProvider>src/theme
中进行系统相关的样式变量配置。
接口修改
- 本系统所有接口都在
utils/api.ts
中集合,interface/request
中定义接口参数的类型,services
中进行 url、method 封装(blob 相关也在这里配置),最后在hooks
中作为方法调用。 - 若使用之前定义的格式,则需要避开装好的接口调用方式,因为原来的接口设计的不太好,请求顺利但是业务逻辑错误会返回400,在这个系统就获取不到返回的数据,所以只能自己hack。这里测试下来可以直接在
hooks
中使用 fetch 来获取完整的接口输出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45export const useLogin = () => {
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['login'],
mutationFn: async (params: {
account: string;
hashed_password: string;
}) => {
const response = await fetch(
'accountApi/v1/ai-platform-control-center/sign-in-password',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
},
);
const res = await response.json();
if (res?.jw_token) {
authorizationUtil.setItems({
Token: res?.jw_token,
Authorization: res?.jw_token,
});
return res;
} else if (res?.trial_times) {
if (res?.trial_times !== 0) {
message.error(
'密码错误,您还有' + res?.trial_times + '次尝试的机会。',
);
}
}
return false;
},
});
return { data, loading, login: mutateAsync };
};
权限
系统设置
- 在左侧菜单渲染层面,通过用户名=admin进行比对,只有admin时才可以渲染出系统设置菜单。
- 可能需要 access 来设定直接输入 URL 层面的跳转,目前查阅资源结论是升级到 @umi/max 比较好,但是考虑到后期还需要移植新 feature,直接改框架成本较高,暂时搁置。
模型设置跳转
- Ragflow 原有逻辑:
- 个人账号未设置模型时,创建知识库、点击知识库之后会跳转;
- 个人账号未设置模型时,创建 Agent、点击 Agent之后会跳转;
- 用了navigate(/***)。猜测是到了路由为 /knowledge/dataset 页面之后进行的判断,该页面 pages/add-knowledge/knowledge-dataset 由 knowledge-file 和 knowledge-chunk 两个组件组成。
- 具体是在弹窗的ok之后跳转的,跳转逻辑在 hooks/user-setting-hooks 的 useFetchTenantInfo 方法中,useFetchTenantInfo(true) 会跳转 /user-setting/model,当前采用将跳转直接注释,若后续有问题需要整体查看一下。
web 端交互
搜索模块
hooks/logic-hooks.ts
中,send 方法请求 /v1/conversation/ask,目前 response 中的 data 为具体内容,若引入深度思考,需要后端对回答进行参数明晰。(大概在215行)。大模型回答可搜索 answer 关键字。- response 中参数如下:
- doc_aggs 为当前切片数组,用分页实现,第一次会返回12条数据,不明原因(看页面展示就用到了10条数据)。但是同时也调用了 /v1/chunk/retrieval_test 返回10条数据,应该用的是这里的数据。
- 后面点击分页之后,请求 /v1/chunk/retrieval_test 都是返回当前页数的10条数据。
1
2
3
4
5
6
7
8
9
10
11
12
13{
answer: 'sse不断新增的完整回答',
reference: {
chunks: [], // 切片
doc_aggs: [
{
doc_name: '文件名',
doc_id: '' // 根据id获取文件图片-一连串查询接口
}
],
total: 29
}
}问答助手模块
pages/chat/chat-container/index.tsx
为聊天内容代码,pages/chat/markdown-content/index.tsx
为回答展示。
/v1/conversation/completion 为大模型回答,answer 参数为回答的具体内容,其中包裹的内容为深度思考,在 markdown 里面是按照html直接渲染的,样式已做了修改。 components/message-item/index.tsx
为单条信息的代码,可设置 showLoudspeaker 为 false 关闭语音播放按钮。具体按钮组代码在components/message-item/group-button
中。
[图片]components/message-item/index.tsx
中用户回答可携带上传成功的文件;助理回答可携带知识库参考来源;MarkdownContent 为回答内容;components/message-input/index.tsx
为输入框代码;用户对输入框的操作在pages/chat/hooks.ts
中;发送代码在src/hooks/logic-hooks.ts
的 useSendMessageWithSse
前后端可能遇到的问题
- 登录时原始返回 authorization、userInfo 和 token,authorization 应该是网关相关,后面的接口都会用到,若用户无法认证,系统会自动跳转登录页面;和统一登录有所差别,开发时需特殊处理;
附录
ragflow
ragflow-plus
umi
深入理解 UMI 框架
qiankun+umi+antd
自定义umi微前端
HTML to JSX
定制主题
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!