diff --git a/docs/ahooks-usage-guide.md b/docs/ahooks-usage-guide.md
new file mode 100644
index 0000000..ea4101e
--- /dev/null
+++ b/docs/ahooks-usage-guide.md
@@ -0,0 +1,287 @@
+# Ahooks 使用指南(基础、进阶与常见用法)
+
+Ahooks 是一套高质量、可复用的 React Hooks 集合,适合在业务工程中快速构建稳定的交互与数据逻辑。本文档覆盖快速上手、基础用法、`useRequest` 核心与进阶、常见场景模板、最佳实践与坑位排查,结合本项目技术栈(React 18 + Vite + MUI)。
+
+目录
+- 为什么选 Ahooks
+- 安装与类型支持
+- 快速上手:页面标题(`useTitle`)
+- 基础能力:状态/事件/定时/存储/性能
+- useRequest 基础与进阶(自动/手动、轮询、缓存、SWR、重试等)
+- 常见场景模板(搜索防抖、任务轮询、分页、依赖请求、自动保存)
+- 最佳实践与常见坑位
+- 速查表与参考资料
+
+## 为什么选用 Ahooks
+
+- 大量生产验证的 Hooks,覆盖状态管理、请求、DOM、浏览器、性能优化等场景。
+- API 统一、可组合,适合封装成业务级自定义 Hook。
+- 降低维护成本:内置防抖/节流、轮询、缓存、并发控制、卸载安全、SWR 等复杂能力。
+
+## 安装与类型支持
+
+```bash
+pnpm add ahooks
+```
+
+TypeScript 用户无需额外配置,Ahooks 提供完整的类型提示。
+
+## 快速上手:更新页面标题(useTitle)
+
+最直接的需求就是“更新页面标题”。`useTitle` 在组件挂载时设置 `document.title`,卸载时可恢复。
+
+```tsx
+import { useTitle } from 'ahooks';
+
+export default function KnowledgeListPage() {
+ useTitle('TERES · 知识库列表', { restoreOnUnmount: true });
+ return (
...
);
+}
+```
+
+如需统一加前后缀,可封装:
+
+```tsx
+import { useTitle } from 'ahooks';
+
+export function usePageTitle(title?: string, opts?: { prefix?: string; suffix?: string }) {
+ const final = [opts?.prefix, title, opts?.suffix].filter(Boolean).join(' · ');
+ useTitle(final || 'TERES', { restoreOnUnmount: true });
+}
+```
+
+## 基础能力(常用 Hooks)
+
+- useBoolean / useToggle:布尔与枚举状态切换
+ ```tsx
+ const [visible, { setTrue, setFalse, toggle }] = useBoolean(false);
+ ```
+
+- useDebounceFn / useThrottleFn:防抖与节流(输入、滚动、窗口变化)
+ ```tsx
+ const { run: onSearchChange, cancel } = useDebounceFn((kw: string) => setKeywords(kw), { wait: 300 });
+ ```
+
+- useEventListener:声明式事件监听(自动清理)
+ ```tsx
+ useEventListener('resize', () => console.log(window.innerWidth), { target: window });
+ ```
+
+- useLocalStorageState / useSessionStorageState:状态持久化
+ ```tsx
+ const [lang, setLang] = useLocalStorageState('lang', { defaultValue: 'zh' });
+ ```
+
+- useInterval / useTimeout:定时任务(自动清理)
+ ```tsx
+ useInterval(() => refresh(), 30000);
+ ```
+
+- useMemoizedFn:稳定函数引用,避免子组件无谓重渲染/解绑
+ ```tsx
+ const onNodeClick = useMemoizedFn((id: string) => openDetail(id));
+ ```
+
+- useLatest:防陈旧闭包,异步/周期函数读取最新数据
+ ```tsx
+ const latestState = useLatest(state);
+ useInterval(() => { doSomething(latestState.current); }, 1000);
+ ```
+
+- useSafeState / useUnmount:卸载安全,避免内存泄漏或报错
+ ```tsx
+ const [data, setData] = useSafeState(null);
+ useUnmount(() => cancelRequest());
+ ```
+
+## useRequest(核心与进阶)
+
+`useRequest` 是 Ahooks 的异步数据管理核心,内置自动/手动触发、轮询、防抖/节流、屏幕聚焦重新请求、错误重试、loading 延迟、SWR(stale-while-revalidate)缓存等能力。[0][1]
+
+### 基础用法(自动请求)
+
+```tsx
+import { useRequest } from 'ahooks';
+import agentService from '@/services/agent_service';
+
+export default function AgentList() {
+ const { data, loading, error, refresh } = useRequest(() => agentService.fetchAgentList());
+ if (loading) return Loading...
;
+ if (error) return Error: {String(error)}
;
+ return {JSON.stringify(data)}
;
+}
+```
+
+### 手动触发与参数管理
+
+```tsx
+const { run, runAsync, loading, params } = useRequest((kw: string) => agentService.searchAgents({ keywords: kw }), {
+ manual: true,
+ onSuccess: (data, [kw]) => console.log('searched', kw, data),
+});
+
+// 触发
+run('rag');
+// 或 await runAsync('rag').catch(console.error);
+```
+
+### 生命周期回调
+
+```tsx
+useRequest(api, {
+ onBefore: (p) => console.log('before', p),
+ onSuccess: (d) => console.log('success', d),
+ onError: (e) => console.error('error', e),
+ onFinally: (p, d, e) => console.log('finally'),
+});
+```
+
+### 刷新上一次请求(refresh)与数据变更(mutate)
+
+```tsx
+const { run, refresh, mutate, data } = useRequest(() => api.getUser(1), { manual: true });
+
+// 修改页面数据,不等待接口返回
+mutate((old) => ({ ...old, name: 'New Name' }));
+// 使用上一次参数重新发起请求
+refresh();
+```
+
+### 取消响应(cancel)与竞态取消
+
+```tsx
+const { run, cancel } = useRequest(api.longTask, { manual: true });
+run();
+// 某些场景需要忽略本次 promise 的响应
+cancel();
+```
+
+### 轮询与停止条件
+
+```tsx
+const { data, cancel } = useRequest(() => agentService.getJobStatus(), {
+ pollingInterval: 5000,
+ pollingWhenHidden: false,
+ onSuccess: (res) => { if (res.done) cancel(); },
+});
+```
+
+### 防抖 / 节流 / 重试 / 延迟 / 焦点刷新
+
+```tsx
+useRequest(searchService.query, {
+ manual: true,
+ debounceWait: 300,
+ throttleWait: 1000,
+ retryCount: 2,
+ loadingDelay: 200,
+ refreshOnWindowFocus: true,
+});
+```
+
+### 缓存(cacheKey)与过期时间(staleTime)
+
+```tsx
+const { data } = useRequest(() => agentService.fetchAgentList(), {
+ cacheKey: 'agent:list',
+ staleTime: 60_000, // 1分钟内复用缓存(SWR 策略)
+});
+```
+
+### 条件就绪(ready)与依赖刷新(refreshDeps)
+
+```tsx
+useRequest(() => api.getById(id), {
+ ready: !!id,
+ refreshDeps: [id],
+});
+```
+
+### 默认参数(defaultParams)
+
+```tsx
+const { data } = useRequest((id: number) => api.getById(id), {
+ defaultParams: [1],
+});
+```
+
+## 常见场景模板
+
+### 搜索输入防抖
+
+```tsx
+const { run: search } = useRequest((kw: string) => api.search({ keywords: kw }), { manual: true });
+const { run: onChange, cancel } = useDebounceFn((kw: string) => search(kw), { wait: 300 });
+```
+
+### 任务状态轮询,完成后停止
+
+```tsx
+const { data, cancel } = useRequest(() => api.getJobStatus(jobId), {
+ pollingInterval: 5000,
+ pollingWhenHidden: false,
+ onSuccess: (res) => { if (res.done) cancel(); },
+});
+```
+
+### 分页列表(前端分页)
+
+```tsx
+const { data, loading } = useRequest(() => api.list({ page, pageSize, keywords }), {
+ refreshDeps: [page, pageSize, keywords],
+});
+```
+
+### 依赖请求(根据上游结果触发下游)
+
+```tsx
+const userReq = useRequest(() => api.getUser(userId), { ready: !!userId });
+const postsReq = useRequest(() => api.getPosts(userReq.data?.id), {
+ ready: !!userReq.data?.id,
+});
+```
+
+### 自动保存(组件生命周期安全)
+
+```tsx
+useInterval(async () => {
+ if (!graphValid) return;
+ await api.setAgentDSL(payload);
+}, 30_000);
+```
+
+## 最佳实践与常见坑位
+
+- 顶层调用 Hooks,避免条件语句中调用,保持顺序一致。
+- 依赖项变动可能导致频繁触发;`useMemoizedFn` 可保持函数引用稳定。
+- 轮询需显式停止;结合 `cancel()` 与 `pollingWhenHidden=false`。
+- 缓存需设置合理 `staleTime`,避免过时数据;SWR 适合只读列表数据。
+- 组件卸载时自动忽略响应,避免卸载后 setState。
+- SSR 环境下注意 `window/document` 可用性,必要时使用 `useIsomorphicLayoutEffect`。
+
+## 与本项目的结合建议
+
+- 知识库列表搜索:将 `setTimeout` 防抖替换为 `useDebounceFn`,代码更简洁且可取消。
+- 任务状态轮询:用 `useRequest` 的 `pollingInterval + cancel()` 替代手写 `setInterval`。
+- 自动保存:使用 `useInterval`,确保随组件生命周期清理(我们已修复泄漏问题)。
+- 事件绑定:用 `useEventListener` 管理 `window`/`document` 事件,自动清理。
+- 页面标题:用 `useTitle` 或统一的 `usePageTitle` 封装。
+
+## 速查表(精选)
+
+- 状态:`useBoolean`、`useToggle`、`useSetState`、`useControllableValue`
+- 请求:`useRequest`
+- DOM/事件:`useEventListener`、`useInViewport`、`useClickAway`
+- 定时:`useInterval`、`useTimeout`
+- 存储:`useLocalStorageState`、`useSessionStorageState`
+- 性能/安全:`useDebounceFn`、`useThrottleFn`、`useMemoizedFn`、`useLatest`、`useSafeState`
+
+## 参考资料
+
+- 官方文档(useRequest 快速上手)[0] https://ahooks.js.org/zh-CN/hooks/use-request/index
+- 官方文档(useRequest 基础用法)[1] https://ahooks.js.org/zh-CN/hooks/use-request/basic
+- 项目仓库:https://github.com/alibaba/hooks
+
+---
+
+实战建议:如果只需要“更新 web 标题”,直接在页面组件里使用 `useTitle`;若需统一标题规范或根据路由动态拼接,封装一个 `usePageTitle` 更可维护。`useRequest` 作为核心请求管理,优先在网络交互中使用它的自动/手动、轮询、缓存与重试能力,替代手写的定时器与状态机。
\ No newline at end of file
diff --git a/index.html b/index.html
index e7f4571..199ae81 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- teres_web_frontend
+ RAG Empowerment System
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..ae94b95
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,35 @@
+
+
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 52850a1..3d88310 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,7 @@ import AppRoutes from './routes';
import SnackbarProvider from './components/Provider/SnackbarProvider';
import DialogProvider from './components/Provider/DialogProvider';
import AuthGuard from './components/AuthGuard';
+import { useTitle } from 'ahooks';
import './locales';
import './utils/request'
@@ -40,6 +41,8 @@ function MaterialUIApp() {
}
function App() {
+ useTitle('RAG Empowerment System');
+
return (
);
diff --git a/src/components/FormField/RadioFormField.tsx b/src/components/FormField/RadioFormField.tsx
new file mode 100644
index 0000000..0bd8a78
--- /dev/null
+++ b/src/components/FormField/RadioFormField.tsx
@@ -0,0 +1,97 @@
+import React from 'react';
+import {
+ Box,
+ Typography,
+ FormControl,
+ FormLabel,
+ RadioGroup,
+ FormControlLabel,
+ Radio,
+ FormHelperText,
+} from '@mui/material';
+import { Controller, useFormContext } from 'react-hook-form';
+import { useTranslation } from 'react-i18next';
+
+export interface RadioOption {
+ value: string | number;
+ label: string;
+ disabled?: boolean;
+}
+
+export interface RadioFormFieldProps {
+ name: string;
+ label?: string;
+ options: RadioOption[];
+ helperText?: string;
+ defaultValue?: string | number;
+ disabled?: boolean;
+ required?: boolean;
+ row?: boolean;
+ size?: 'small' | 'medium';
+ onChangeValue?: (value: string | number) => void;
+}
+
+export const RadioFormField: React.FC = ({
+ name,
+ label,
+ options,
+ helperText,
+ defaultValue = '',
+ disabled = false,
+ required = false,
+ row = true,
+ size = 'medium',
+ onChangeValue,
+}) => {
+ const { control } = useFormContext();
+ const { t } = useTranslation();
+
+ return (
+
+
+ {required && *}
+ {label}
+
+ (
+
+ {label &&
+ {label}
+ }
+ {
+ field.onChange(value);
+ onChangeValue?.(value);
+ }}
+ >
+ {options.map((item) => (
+ }
+ label={item.label}
+ />
+ ))}
+
+ {(error || helperText) && (
+
+ {error?.message || helperText}
+
+ )}
+
+ )}
+ />
+
+ );
+};
+
+export default RadioFormField;
\ No newline at end of file
diff --git a/src/components/FormField/index.ts b/src/components/FormField/index.ts
index 873c6b4..2b35cb6 100644
--- a/src/components/FormField/index.ts
+++ b/src/components/FormField/index.ts
@@ -9,6 +9,7 @@ export { SliderFormField } from './SliderFormField';
export { TextFormField } from './TextFormField';
export { DatePickerFormField } from './DatePickerFormField';
export { CheckboxFormField } from './CheckboxFormField';
+export { RadioFormField } from './RadioFormField';
// 类型导出
export type { SliderInputFormFieldProps } from './SliderInputFormField';
diff --git a/src/hooks/agent-hooks.ts b/src/hooks/agent-hooks.ts
index c3ce55d..da3cdf1 100644
--- a/src/hooks/agent-hooks.ts
+++ b/src/hooks/agent-hooks.ts
@@ -44,7 +44,13 @@ export const useAgentList = (initialParams?: IAgentPaginationParams) => {
setLoading(true);
setError(null);
try {
- const response = await agentService.listCanvas(params);
+ const envMode = import.meta.env.MODE;
+ let response: any = null;
+ if (envMode === 'flask') {
+ response = await agentService.teamlistCanvas(params);
+ } else {
+ response = await agentService.listCanvas(params);
+ }
const res = response.data || {};
logger.info('useAgentList fetchAgentList', res);
const data = res.data
diff --git a/src/interfaces/database/agent.ts b/src/interfaces/database/agent.ts
index a96c317..e55a743 100644
--- a/src/interfaces/database/agent.ts
+++ b/src/interfaces/database/agent.ts
@@ -268,25 +268,6 @@ export interface IAgentLogMessage {
id: string;
}
-export interface IPipeLineListRequest {
- page?: number;
- page_size?: number;
- keywords?: string;
- orderby?: string;
- desc?: boolean;
- // canvas_category?: AgentCategory;
-}
-
-// {
-// "create_date": "Thu, 06 Nov 2025 15:52:42 GMT",
-// "create_time": 1762415562692,
-// "id": "931d1cfabae511f097ca36b0b158b556",
-// "title": "blank_2025_11_06_15_52_42",
-// "update_date": "Thu, 06 Nov 2025 15:52:42 GMT",
-// "update_time": 1762415562692,
-// "user_canvas_id": "1edc3ddabadb11f08d0636b0b158b556"
-// }
-
export interface IAgentVersionItem {
id: string;
title: string;
diff --git a/src/interfaces/database/knowledge.ts b/src/interfaces/database/knowledge.ts
index a9f3043..1e3e98d 100644
--- a/src/interfaces/database/knowledge.ts
+++ b/src/interfaces/database/knowledge.ts
@@ -102,11 +102,11 @@ export interface IKnowledge {
/** 解析器ID */
parser_id: string;
/** 管道ID */
- pipeline_id: string;
+ pipeline_id?: string;
/** 管道名称 */
- pipeline_name: string;
+ pipeline_name?: string;
/** 管道头像 */
- pipeline_avatar: string;
+ pipeline_avatar?: string;
/** 权限设置 */
permission: string;
/** 相似度阈值 */
diff --git a/src/interfaces/request/agent.ts b/src/interfaces/request/agent.ts
index c08b392..5924607 100644
--- a/src/interfaces/request/agent.ts
+++ b/src/interfaces/request/agent.ts
@@ -1,3 +1,4 @@
+import { AgentCategory } from "../../constants/agent";
import { DSL } from "../database/agent";
/**
@@ -7,6 +8,9 @@ export interface IAgentPaginationParams {
keywords?: string;
page?: number;
page_size?: number;
+ orderby?: string;
+ desc?: boolean;
+ canvas_category?: AgentCategory;
}
diff --git a/src/locales/en.ts b/src/locales/en.ts
index 9ec42e6..3b1cb62 100644
--- a/src/locales/en.ts
+++ b/src/locales/en.ts
@@ -644,6 +644,7 @@ export default {
eidtLinkDataPipeline: 'Edit Data Pipeline',
linkPipelineSetTip: 'Manage data pipeline linkage with this dataset',
default: 'Default',
+ buildMode: 'Build Mode',
dataPipeline: 'Data Pipeline',
linkDataPipeline: 'Link Data Pipeline',
enableAutoGenerate: 'Enable Auto Generate',
diff --git a/src/pages/agent/components/AgentTopbar.tsx b/src/pages/agent/components/AgentTopbar.tsx
index febb913..1c5210f 100644
--- a/src/pages/agent/components/AgentTopbar.tsx
+++ b/src/pages/agent/components/AgentTopbar.tsx
@@ -13,7 +13,7 @@ export interface AgentTopbarProps {
onOpenSettings?: () => void;
}
-export default function AgentTopbar({ title, id, onRefresh, subtitle, actionsRight, onOpenVersions, onOpenSettings }: AgentTopbarProps) {
+export default function AgentTopbar({ title, onRefresh, subtitle, actionsRight, onOpenVersions, onOpenSettings }: AgentTopbarProps) {
const { t } = useTranslation();
return (
);
}
@@ -341,6 +341,42 @@ export function TOCEnhanceItem() {
);
}
+// Pipeline 选择器
+export function PipelineSelectorItem() {
+ const { t } = useTranslation();
+ const [options, setOptions] = useState([]);
+
+ useEffect(() => {
+ const fetchPipelines = async () => {
+ try {
+ const envMode = import.meta.env.MODE;
+ const service = envMode === 'flask' ? agentService.teamlistCanvas : agentService.listCanvas;
+ const res = await service({ canvas_category: AgentCategory.DataflowCanvas, page_size: 100 });
+ const data = res?.data?.data || {};
+ const list = data.canvas || [];
+ const mapped: SelectOption[] = list.map((item: any) => ({
+ value: item.id,
+ label: item.title || item.name || item.id,
+ disabled: false,
+ }));
+ setOptions(mapped);
+ } catch (err) {
+ console.error('Failed to fetch pipelines:', err);
+ }
+ };
+ fetchPipelines();
+ }, []);
+
+ return (
+
+ );
+}
+
/* ============================================================================
* 第二部分:RAPTOR策略 (RAPTOR Strategy)
* ============================================================================ */
@@ -510,7 +546,33 @@ export function CommunityReportItem() {
* 组合配置组件 (Composite Configuration Components)
* ============================================================================ */
-// RAPTOR策略配置项组合
+/*
+ * 基础配置项 (Basic Configuration Items)
+ */
+export function BasicConfigItems() {
+ return (
+
+ {/* PDF解析器 */}
+
+ {/* 建议文本块大小 */}
+
+ {/* 文本分段标识符 */}
+
+ {/* 目录增强 */}
+
+ {/* 自动关键词提取 */}
+
+ {/* 自动问题提取 */}
+
+ {/* 表格转HTML */}
+
+
+ );
+}
+
+/**
+ * RAPTOR策略配置项组合
+ */
export function RaptorConfigItems() {
return (
@@ -530,7 +592,9 @@ export function RaptorConfigItems() {
);
}
-// 知识图谱配置项组合
+/**
+ * 知识图谱配置项组合
+ */
export function KnowledgeGraphConfigItems() {
return (
diff --git a/src/pages/knowledge/configuration/naive.tsx b/src/pages/knowledge/configuration/naive.tsx
index 64eca7f..acf33b6 100644
--- a/src/pages/knowledge/configuration/naive.tsx
+++ b/src/pages/knowledge/configuration/naive.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import {
Box,
Typography,
@@ -13,20 +13,23 @@ import { useTranslation } from 'react-i18next';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import {
ChunkMethodItem,
- ChunkTokenNumberItem,
- AutoKeywordsItem,
- AutoQuestionsItem,
- HtmlForExcelItem,
- LayoutRecognizeItem,
- DelimiterItem,
- TOCEnhanceItem,
RaptorConfigItems,
- KnowledgeGraphConfigItems
+ KnowledgeGraphConfigItems,
+ PipelineSelectorItem,
+ BasicConfigItems,
} from './common-items';
+import { RadioFormField } from '@/components/FormField';
export function NaiveConfiguration() {
- const { formState: { errors } } = useFormContext();
+ const { formState: { errors }, watch } = useFormContext();
const { t } = useTranslation();
+ const [buildMode, setBuildMode] = useState<'buildIn' | 'pipeline'>('buildIn');
+
+ // 根据表单的 pipeline_id 自动切换 buildMode
+ const pipelineId = watch('pipeline_id');
+ useEffect(() => {
+ setBuildMode(pipelineId ? 'pipeline' : 'buildIn');
+ }, [pipelineId]);
return (
@@ -38,26 +41,27 @@ export function NaiveConfiguration() {
{/* 切片方法 */}
-
-
+
+ setBuildMode(String(v) as 'buildIn' | 'pipeline')}
+ />
+ {buildMode === 'buildIn' ? (
+
+ ) : (
+
+ )}
-
-
- {/* PDF解析器 */}
-
- {/* 建议文本块大小 */}
-
- {/* 文本分段标识符 */}
-
- {/* 目录增强 */}
-
- {/* 自动关键词提取 */}
-
- {/* 自动问题提取 */}
-
- {/* 表格转HTML */}
-
+
+ {buildMode === 'buildIn' && (
+
+ )}
diff --git a/src/pages/knowledge/setting.tsx b/src/pages/knowledge/setting.tsx
index ba71e59..71050e3 100644
--- a/src/pages/knowledge/setting.tsx
+++ b/src/pages/knowledge/setting.tsx
@@ -136,6 +136,7 @@ function KnowledgeBaseSetting() {
parser_id: data.parser_id,
embd_id: data.embd_id,
parser_config: data.parser_config,
+ pipeline_id: data.pipeline_id,
};
await updateKnowledgeModelConfig(configData);
diff --git a/src/services/agent_service.ts b/src/services/agent_service.ts
index e07abf5..f63d9ec 100644
--- a/src/services/agent_service.ts
+++ b/src/services/agent_service.ts
@@ -13,6 +13,12 @@ const agentService = {
* 获取团队下的Canvas列表
*/
listCanvas: (params?: IAgentPaginationParams) => {
+ return request.get(api.listCanvas, { params });
+ },
+ /**
+ * 获取团队下的Canvas列表
+ */
+ teamlistCanvas: (params?: IAgentPaginationParams) => {
return request.get(api.listTeamCanvas, { params });
},
/**