feat(knowledge): add pipeline support and build mode selection
refactor(configuration): reorganize naive config form with pipeline selector feat(form): add RadioFormField component for build mode selection docs: add ahooks usage guide for common patterns style: update app title and favicon chore: clean up unused agent interfaces
This commit is contained in:
287
docs/ahooks-usage-guide.md
Normal file
287
docs/ahooks-usage-guide.md
Normal file
@@ -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 (<div>...</div>);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如需统一加前后缀,可封装:
|
||||||
|
|
||||||
|
```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<any>(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 <div>Loading...</div>;
|
||||||
|
if (error) return <div>Error: {String(error)}</div>;
|
||||||
|
return <div>{JSON.stringify(data)}</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动触发与参数管理
|
||||||
|
|
||||||
|
```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` 作为核心请求管理,优先在网络交互中使用它的自动/手动、轮询、缓存与重试能力,替代手写的定时器与状态机。
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>teres_web_frontend</title>
|
<title>RAG Empowerment System</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
35
public/logo.svg
Normal file
35
public/logo.svg
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.magenta{stroke:#d61c57; fill:none; stroke-width:12; stroke-linecap:round; stroke-linejoin:round}
|
||||||
|
.dark{stroke:#2f3439; fill:none; stroke-width:12; stroke-linecap:round; stroke-linejoin:round}
|
||||||
|
.light{stroke:#bfc5cb; fill:none; stroke-width:12; stroke-linecap:round; stroke-linejoin:round}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Top arrow -->
|
||||||
|
<path d="M128 28 L164 64 L144 64 L144 92 L112 92 L112 64 L92 64 Z" fill="#d61c57"/>
|
||||||
|
|
||||||
|
<!-- Light branches -->
|
||||||
|
<path class="light" d="M112 92 L92 112 L92 128"/>
|
||||||
|
<path class="light" d="M144 92 L164 112 L164 128"/>
|
||||||
|
|
||||||
|
<!-- Red mid arms -->
|
||||||
|
<path class="magenta" d="M108 120 L76 142 L76 164"/>
|
||||||
|
<path class="magenta" d="M148 120 L180 142 L180 164"/>
|
||||||
|
|
||||||
|
<!-- Center dot -->
|
||||||
|
<circle cx="128" cy="128" r="8" fill="#d61c57"/>
|
||||||
|
|
||||||
|
<!-- Dark wings -->
|
||||||
|
<path class="dark" d="M128 136 L96 156 L92 184"/>
|
||||||
|
<path class="dark" d="M128 136 L160 156 L164 184"/>
|
||||||
|
|
||||||
|
<!-- Dark lower circuits -->
|
||||||
|
<path class="dark" d="M128 172 L128 202"/>
|
||||||
|
<circle cx="112" cy="208" r="7" fill="#2f3439"/>
|
||||||
|
<circle cx="144" cy="208" r="7" fill="#2f3439"/>
|
||||||
|
<path class="dark" d="M112 208 L100 188"/>
|
||||||
|
<path class="dark" d="M144 208 L156 188"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -5,6 +5,7 @@ import AppRoutes from './routes';
|
|||||||
import SnackbarProvider from './components/Provider/SnackbarProvider';
|
import SnackbarProvider from './components/Provider/SnackbarProvider';
|
||||||
import DialogProvider from './components/Provider/DialogProvider';
|
import DialogProvider from './components/Provider/DialogProvider';
|
||||||
import AuthGuard from './components/AuthGuard';
|
import AuthGuard from './components/AuthGuard';
|
||||||
|
import { useTitle } from 'ahooks';
|
||||||
|
|
||||||
import './locales';
|
import './locales';
|
||||||
import './utils/request'
|
import './utils/request'
|
||||||
@@ -40,6 +41,8 @@ function MaterialUIApp() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
useTitle('RAG Empowerment System');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MaterialUIApp />
|
<MaterialUIApp />
|
||||||
);
|
);
|
||||||
|
|||||||
97
src/components/FormField/RadioFormField.tsx
Normal file
97
src/components/FormField/RadioFormField.tsx
Normal file
@@ -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<RadioFormFieldProps> = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
options,
|
||||||
|
helperText,
|
||||||
|
defaultValue = '',
|
||||||
|
disabled = false,
|
||||||
|
required = false,
|
||||||
|
row = true,
|
||||||
|
size = 'medium',
|
||||||
|
onChangeValue,
|
||||||
|
}) => {
|
||||||
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
|
{required && <span style={{ color: 'red' }}>*</span>}
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<Controller
|
||||||
|
name={name}
|
||||||
|
control={control}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
rules={{
|
||||||
|
required: required ? 'This field is required' : false,
|
||||||
|
}}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl component="fieldset" error={!!error}>
|
||||||
|
{label && <FormLabel component="legend" sx={{ display: 'none' }}>
|
||||||
|
{label}
|
||||||
|
</FormLabel>}
|
||||||
|
<RadioGroup
|
||||||
|
{...field}
|
||||||
|
name={name}
|
||||||
|
row={row}
|
||||||
|
onChange={(_, value) => {
|
||||||
|
field.onChange(value);
|
||||||
|
onChangeValue?.(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{options.map((item) => (
|
||||||
|
<FormControlLabel
|
||||||
|
key={item.value}
|
||||||
|
value={item.value}
|
||||||
|
control={<Radio size={size} disabled={disabled || item.disabled} />}
|
||||||
|
label={item.label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
{(error || helperText) && (
|
||||||
|
<FormHelperText>
|
||||||
|
{error?.message || helperText}
|
||||||
|
</FormHelperText>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RadioFormField;
|
||||||
@@ -9,6 +9,7 @@ export { SliderFormField } from './SliderFormField';
|
|||||||
export { TextFormField } from './TextFormField';
|
export { TextFormField } from './TextFormField';
|
||||||
export { DatePickerFormField } from './DatePickerFormField';
|
export { DatePickerFormField } from './DatePickerFormField';
|
||||||
export { CheckboxFormField } from './CheckboxFormField';
|
export { CheckboxFormField } from './CheckboxFormField';
|
||||||
|
export { RadioFormField } from './RadioFormField';
|
||||||
|
|
||||||
// 类型导出
|
// 类型导出
|
||||||
export type { SliderInputFormFieldProps } from './SliderInputFormField';
|
export type { SliderInputFormFieldProps } from './SliderInputFormField';
|
||||||
|
|||||||
@@ -44,7 +44,13 @@ export const useAgentList = (initialParams?: IAgentPaginationParams) => {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
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 || {};
|
const res = response.data || {};
|
||||||
logger.info('useAgentList fetchAgentList', res);
|
logger.info('useAgentList fetchAgentList', res);
|
||||||
const data = res.data
|
const data = res.data
|
||||||
|
|||||||
@@ -268,25 +268,6 @@ export interface IAgentLogMessage {
|
|||||||
id: string;
|
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 {
|
export interface IAgentVersionItem {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -102,11 +102,11 @@ export interface IKnowledge {
|
|||||||
/** 解析器ID */
|
/** 解析器ID */
|
||||||
parser_id: string;
|
parser_id: string;
|
||||||
/** 管道ID */
|
/** 管道ID */
|
||||||
pipeline_id: string;
|
pipeline_id?: string;
|
||||||
/** 管道名称 */
|
/** 管道名称 */
|
||||||
pipeline_name: string;
|
pipeline_name?: string;
|
||||||
/** 管道头像 */
|
/** 管道头像 */
|
||||||
pipeline_avatar: string;
|
pipeline_avatar?: string;
|
||||||
/** 权限设置 */
|
/** 权限设置 */
|
||||||
permission: string;
|
permission: string;
|
||||||
/** 相似度阈值 */
|
/** 相似度阈值 */
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AgentCategory } from "../../constants/agent";
|
||||||
import { DSL } from "../database/agent";
|
import { DSL } from "../database/agent";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,6 +8,9 @@ export interface IAgentPaginationParams {
|
|||||||
keywords?: string;
|
keywords?: string;
|
||||||
page?: number;
|
page?: number;
|
||||||
page_size?: number;
|
page_size?: number;
|
||||||
|
orderby?: string;
|
||||||
|
desc?: boolean;
|
||||||
|
canvas_category?: AgentCategory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -644,6 +644,7 @@ export default {
|
|||||||
eidtLinkDataPipeline: 'Edit Data Pipeline',
|
eidtLinkDataPipeline: 'Edit Data Pipeline',
|
||||||
linkPipelineSetTip: 'Manage data pipeline linkage with this dataset',
|
linkPipelineSetTip: 'Manage data pipeline linkage with this dataset',
|
||||||
default: 'Default',
|
default: 'Default',
|
||||||
|
buildMode: 'Build Mode',
|
||||||
dataPipeline: 'Data Pipeline',
|
dataPipeline: 'Data Pipeline',
|
||||||
linkDataPipeline: 'Link Data Pipeline',
|
linkDataPipeline: 'Link Data Pipeline',
|
||||||
enableAutoGenerate: 'Enable Auto Generate',
|
enableAutoGenerate: 'Enable Auto Generate',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export interface AgentTopbarProps {
|
|||||||
onOpenSettings?: () => void;
|
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();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
@@ -15,6 +15,7 @@ import { useFormContext, Controller } from 'react-hook-form';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { t as translate } from 'i18next';
|
import { t as translate } from 'i18next';
|
||||||
import { DOCUMENT_PARSER_TYPES, LLM_MODEL_TYPES, type LlmModelType } from '@/constants/knowledge';
|
import { DOCUMENT_PARSER_TYPES, LLM_MODEL_TYPES, type LlmModelType } from '@/constants/knowledge';
|
||||||
|
import { AgentCategory } from '@/constants/agent';
|
||||||
import { useSelectChunkMethodList } from '../hooks';
|
import { useSelectChunkMethodList } from '../hooks';
|
||||||
import { useEmbeddingModelOptions, useLlmOptionsByModelType } from '@/hooks/llm-hooks';
|
import { useEmbeddingModelOptions, useLlmOptionsByModelType } from '@/hooks/llm-hooks';
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
TextFormField,
|
TextFormField,
|
||||||
type SelectOption,
|
type SelectOption,
|
||||||
} from '@/components/FormField';
|
} from '@/components/FormField';
|
||||||
|
import agentService from '@/services/agent_service';
|
||||||
import { LlmSvgIcon } from '@/components/AppSvgIcon';
|
import { LlmSvgIcon } from '@/components/AppSvgIcon';
|
||||||
import {type LLMFactory } from '@/constants/llm';
|
import {type LLMFactory } from '@/constants/llm';
|
||||||
import { getFactoryIconName } from '@/utils/common';
|
import { getFactoryIconName } from '@/utils/common';
|
||||||
@@ -153,7 +155,6 @@ export function ChunkMethodItem() {
|
|||||||
export function TagsItem() {
|
export function TagsItem() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const tagsOptions: SelectOption[] = [
|
const tagsOptions: SelectOption[] = [
|
||||||
{ value: '', label: t('common.pleaseSelect') },
|
|
||||||
// 这里可以根据实际需求添加标签选项
|
// 这里可以根据实际需求添加标签选项
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -162,8 +163,7 @@ export function TagsItem() {
|
|||||||
name="parser_config.tags"
|
name="parser_config.tags"
|
||||||
label={t('knowledge.config.tags')}
|
label={t('knowledge.config.tags')}
|
||||||
options={tagsOptions}
|
options={tagsOptions}
|
||||||
defaultValue=""
|
displayEmpty={false}
|
||||||
displayEmpty
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -341,6 +341,42 @@ export function TOCEnhanceItem() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pipeline 选择器
|
||||||
|
export function PipelineSelectorItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [options, setOptions] = useState<SelectOption[]>([]);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<SelectFormField
|
||||||
|
name="pipeline_id"
|
||||||
|
label={t('knowledgeConfiguration.dataFlow')}
|
||||||
|
options={options}
|
||||||
|
displayEmpty={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================================
|
/* ============================================================================
|
||||||
* 第二部分:RAPTOR策略 (RAPTOR Strategy)
|
* 第二部分:RAPTOR策略 (RAPTOR Strategy)
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
@@ -510,7 +546,33 @@ export function CommunityReportItem() {
|
|||||||
* 组合配置组件 (Composite Configuration Components)
|
* 组合配置组件 (Composite Configuration Components)
|
||||||
* ============================================================================ */
|
* ============================================================================ */
|
||||||
|
|
||||||
// RAPTOR策略配置项组合
|
/*
|
||||||
|
* 基础配置项 (Basic Configuration Items)
|
||||||
|
*/
|
||||||
|
export function BasicConfigItems() {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
{/* PDF解析器 */}
|
||||||
|
<LayoutRecognizeItem />
|
||||||
|
{/* 建议文本块大小 */}
|
||||||
|
<ChunkTokenNumberItem />
|
||||||
|
{/* 文本分段标识符 */}
|
||||||
|
<DelimiterItem />
|
||||||
|
{/* 目录增强 */}
|
||||||
|
<TOCEnhanceItem />
|
||||||
|
{/* 自动关键词提取 */}
|
||||||
|
<AutoKeywordsItem />
|
||||||
|
{/* 自动问题提取 */}
|
||||||
|
<AutoQuestionsItem />
|
||||||
|
{/* 表格转HTML */}
|
||||||
|
<HtmlForExcelItem />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAPTOR策略配置项组合
|
||||||
|
*/
|
||||||
export function RaptorConfigItems() {
|
export function RaptorConfigItems() {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
@@ -530,7 +592,9 @@ export function RaptorConfigItems() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 知识图谱配置项组合
|
/**
|
||||||
|
* 知识图谱配置项组合
|
||||||
|
*/
|
||||||
export function KnowledgeGraphConfigItems() {
|
export function KnowledgeGraphConfigItems() {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -13,20 +13,23 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
|
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
|
||||||
import {
|
import {
|
||||||
ChunkMethodItem,
|
ChunkMethodItem,
|
||||||
ChunkTokenNumberItem,
|
|
||||||
AutoKeywordsItem,
|
|
||||||
AutoQuestionsItem,
|
|
||||||
HtmlForExcelItem,
|
|
||||||
LayoutRecognizeItem,
|
|
||||||
DelimiterItem,
|
|
||||||
TOCEnhanceItem,
|
|
||||||
RaptorConfigItems,
|
RaptorConfigItems,
|
||||||
KnowledgeGraphConfigItems
|
KnowledgeGraphConfigItems,
|
||||||
|
PipelineSelectorItem,
|
||||||
|
BasicConfigItems,
|
||||||
} from './common-items';
|
} from './common-items';
|
||||||
|
import { RadioFormField } from '@/components/FormField';
|
||||||
|
|
||||||
export function NaiveConfiguration() {
|
export function NaiveConfiguration() {
|
||||||
const { formState: { errors } } = useFormContext();
|
const { formState: { errors }, watch } = useFormContext();
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<ConfigurationFormContainer>
|
<ConfigurationFormContainer>
|
||||||
@@ -38,26 +41,27 @@ export function NaiveConfiguration() {
|
|||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
{/* 切片方法 */}
|
{/* 切片方法 */}
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3, display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
<RadioFormField
|
||||||
|
name="build_mode"
|
||||||
|
defaultValue="buildIn"
|
||||||
|
options={[
|
||||||
|
{ value: 'buildIn', label: 'Built-in' },
|
||||||
|
{ value: 'pipeline', label: 'Pipeline' },
|
||||||
|
]}
|
||||||
|
onChangeValue={(v) => setBuildMode(String(v) as 'buildIn' | 'pipeline')}
|
||||||
|
/>
|
||||||
|
{buildMode === 'buildIn' ? (
|
||||||
<ChunkMethodItem />
|
<ChunkMethodItem />
|
||||||
|
) : (
|
||||||
|
<PipelineSelectorItem />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 3 }}>
|
{buildMode === 'buildIn' && (
|
||||||
{/* PDF解析器 */}
|
<BasicConfigItems />
|
||||||
<LayoutRecognizeItem />
|
)}
|
||||||
{/* 建议文本块大小 */}
|
|
||||||
<ChunkTokenNumberItem />
|
|
||||||
{/* 文本分段标识符 */}
|
|
||||||
<DelimiterItem />
|
|
||||||
{/* 目录增强 */}
|
|
||||||
<TOCEnhanceItem />
|
|
||||||
{/* 自动关键词提取 */}
|
|
||||||
<AutoKeywordsItem />
|
|
||||||
{/* 自动问题提取 */}
|
|
||||||
<AutoQuestionsItem />
|
|
||||||
{/* 表格转HTML */}
|
|
||||||
<HtmlForExcelItem />
|
|
||||||
</Box>
|
</Box>
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ function KnowledgeBaseSetting() {
|
|||||||
parser_id: data.parser_id,
|
parser_id: data.parser_id,
|
||||||
embd_id: data.embd_id,
|
embd_id: data.embd_id,
|
||||||
parser_config: data.parser_config,
|
parser_config: data.parser_config,
|
||||||
|
pipeline_id: data.pipeline_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateKnowledgeModelConfig(configData);
|
await updateKnowledgeModelConfig(configData);
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ const agentService = {
|
|||||||
* 获取团队下的Canvas列表
|
* 获取团队下的Canvas列表
|
||||||
*/
|
*/
|
||||||
listCanvas: (params?: IAgentPaginationParams) => {
|
listCanvas: (params?: IAgentPaginationParams) => {
|
||||||
|
return request.get(api.listCanvas, { params });
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取团队下的Canvas列表
|
||||||
|
*/
|
||||||
|
teamlistCanvas: (params?: IAgentPaginationParams) => {
|
||||||
return request.get(api.listTeamCanvas, { params });
|
return request.get(api.listTeamCanvas, { params });
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user