mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
Update doc (#5934)
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions
Some checks are pending
Document deploy / sync-images (push) Waiting to run
Document deploy / generate-timestamp (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.cn suffix:cn]) (push) Blocked by required conditions
Document deploy / build-images (map[domain:https://fastgpt.io suffix:io]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.cn kube_config:KUBE_CONFIG_CN suffix:cn]) (push) Blocked by required conditions
Document deploy / update-images (map[deployment:fastgpt-docs domain:https://fastgpt.io kube_config:KUBE_CONFIG_IO suffix:io]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / get-vars (push) Waiting to run
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:amd64 runs-on:ubuntu-24.04]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / build-fastgpt-images (map[arch:arm64 runs-on:ubuntu-24.04-arm]) (push) Blocked by required conditions
Build FastGPT images in Personal warehouse / release-fastgpt-images (push) Blocked by required conditions
* fix: text split * remove test * doc * doc * feat: support quick create dataset in app (#5940) * feat: support quick create dataset in app * doc * perf: create dataset modal * remove log --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
parent
2c681bcdd1
commit
7b82e1dcf8
|
|
@ -57,7 +57,7 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||||
- [x] 知识库单点搜索测试
|
- [x] 知识库单点搜索测试
|
||||||
- [x] 对话时反馈引用并可修改与删除
|
- [x] 对话时反馈引用并可修改与删除
|
||||||
- [x] 完整调用链路日志
|
- [x] 完整调用链路日志
|
||||||
- [ ] 应用评测
|
- [x] 应用评测
|
||||||
- [ ] 高级编排 DeBug 调试模式
|
- [ ] 高级编排 DeBug 调试模式
|
||||||
- [ ] 应用节点日志
|
- [ ] 应用节点日志
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ description: 'FastGPT V4.14.2 更新说明'
|
||||||
|
|
||||||
1. 封装底层 Agent Call 方式,支持工具连续调用时上下文的压缩,以及单个工具长响应的压缩。
|
1. 封装底层 Agent Call 方式,支持工具连续调用时上下文的压缩,以及单个工具长响应的压缩。
|
||||||
2. 模板市场新 UI。
|
2. 模板市场新 UI。
|
||||||
|
3. 支持 Agent 编辑页快速创建知识库。
|
||||||
|
|
||||||
## ⚙️ 优化
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
|
@ -19,3 +20,16 @@ description: 'FastGPT V4.14.2 更新说明'
|
||||||
|
|
||||||
1. 简易应用模板未正常转化。
|
1. 简易应用模板未正常转化。
|
||||||
2. 工具调用中,包含两个以上连续用户选择时候,第二个用户选择异常。
|
2. 工具调用中,包含两个以上连续用户选择时候,第二个用户选择异常。
|
||||||
|
3. 门户中,团队应用类型错误。
|
||||||
|
|
||||||
|
## 插件
|
||||||
|
|
||||||
|
1. 修复:子工具头像丢失。
|
||||||
|
2. 修复:模型头像丢失。
|
||||||
|
3. 修复:Worker 中错误引用 mongoose 依赖,导致超过 10s 的工具运行报错。
|
||||||
|
4. 优化:开发环境热更新时,不重复上传静态文件。
|
||||||
|
5. 新增:5118 SEO 关键词挖掘工具。
|
||||||
|
6. 新增:Tavity 内容提取高级配置。网页站点地图工具。
|
||||||
|
7. 新增:微信公众号工具集。
|
||||||
|
8. 新增:文档对比工具。
|
||||||
|
9. 新增:kimiV2 和 GPT5.1 模型预设。
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
"document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00",
|
"document/content/docs/upgrading/4-13/4132.mdx": "2025-10-21T11:46:53+08:00",
|
||||||
"document/content/docs/upgrading/4-14/4140.mdx": "2025-11-06T15:43:00+08:00",
|
"document/content/docs/upgrading/4-14/4140.mdx": "2025-11-06T15:43:00+08:00",
|
||||||
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-12T12:19:02+08:00",
|
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-12T12:19:02+08:00",
|
||||||
"document/content/docs/upgrading/4-14/4142.mdx": "2025-11-14T13:21:17+08:00",
|
"document/content/docs/upgrading/4-14/4142.mdx": "2025-11-17T19:34:52+08:00",
|
||||||
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
|
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
|
||||||
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
|
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
|
||||||
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
|
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ export type AppSimpleEditFormType = {
|
||||||
[NodeInputKeyEnum.aiChatJsonSchema]?: string;
|
[NodeInputKeyEnum.aiChatJsonSchema]?: string;
|
||||||
};
|
};
|
||||||
dataset: {
|
dataset: {
|
||||||
datasets: SelectedDatasetType;
|
datasets: SelectedDatasetType[];
|
||||||
} & AppDatasetSearchParamsType;
|
} & AppDatasetSearchParamsType;
|
||||||
selectedTools: FlowNodeTemplateType[];
|
selectedTools: FlowNodeTemplateType[];
|
||||||
chatConfig: AppChatConfigType;
|
chatConfig: AppChatConfigType;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ import type {
|
||||||
SearchScoreTypeEnum,
|
SearchScoreTypeEnum,
|
||||||
TrainingModeEnum,
|
TrainingModeEnum,
|
||||||
ChunkSettingModeEnum,
|
ChunkSettingModeEnum,
|
||||||
ChunkTriggerConfigTypeEnum
|
ChunkTriggerConfigTypeEnum,
|
||||||
|
ParagraphChunkAIModeEnum
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import type { DatasetPermission } from '../../support/permission/dataset/controller';
|
import type { DatasetPermission } from '../../support/permission/dataset/controller';
|
||||||
import type {
|
import type {
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ export type SelectedDatasetType = {
|
||||||
avatar: string;
|
avatar: string;
|
||||||
name: string;
|
name: string;
|
||||||
vectorModel: EmbeddingModelItemType;
|
vectorModel: EmbeddingModelItemType;
|
||||||
}[];
|
};
|
||||||
|
|
||||||
/* http node */
|
/* http node */
|
||||||
export type HttpParamAndHeaderItemType = {
|
export type HttpParamAndHeaderItemType = {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { getDatasetSearchToolResponsePrompt } from '../../../../../global/core/a
|
||||||
import { getNodeErrResponse } from '../utils';
|
import { getNodeErrResponse } from '../utils';
|
||||||
|
|
||||||
type DatasetSearchProps = ModuleDispatchProps<{
|
type DatasetSearchProps = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType[];
|
||||||
[NodeInputKeyEnum.datasetSimilarity]: number;
|
[NodeInputKeyEnum.datasetSimilarity]: number;
|
||||||
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
||||||
[NodeInputKeyEnum.userChatInput]?: string;
|
[NodeInputKeyEnum.userChatInput]?: string;
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ const MyModal = ({
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Box ml={3} color={'myGray.900'} fontWeight={'500'}>
|
<Box ml={iconSrc ? 3 : 0} color={'myGray.900'} fontWeight={'500'}>
|
||||||
{title}
|
{title}
|
||||||
</Box>
|
</Box>
|
||||||
<Box flex={1} />
|
<Box flex={1} />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"Add_tool": "Add tool",
|
"Add_tool": "Add tool",
|
||||||
"AutoOptimize": "Automatic optimization",
|
"AutoOptimize": "Automatic optimization",
|
||||||
"Click_to_delete_this_field": "Click to delete this field",
|
"Click_to_delete_this_field": "Click to delete this field",
|
||||||
|
"Create_dataset": "Create a knowledge base",
|
||||||
"Custom_params": "input parameters",
|
"Custom_params": "input parameters",
|
||||||
"Edit_tool": "Edit tool",
|
"Edit_tool": "Edit tool",
|
||||||
"Filed_is_deprecated": "This field is deprecated",
|
"Filed_is_deprecated": "This field is deprecated",
|
||||||
|
|
@ -126,6 +127,10 @@
|
||||||
"custom_plugin_user_guide_label": "User Guide",
|
"custom_plugin_user_guide_label": "User Guide",
|
||||||
"custom_plugin_user_guide_placeholder": "Use markdown syntax",
|
"custom_plugin_user_guide_placeholder": "Use markdown syntax",
|
||||||
"dataset": "dataset",
|
"dataset": "dataset",
|
||||||
|
"dataset.Select_dataset_model_tip": "Only knowledge bases with the same index model can be selected",
|
||||||
|
"dataset.create_dataset_tips": "For more advanced operations, please go to",
|
||||||
|
"dataset_create_success": "The knowledge base was created successfully and files are being indexed in the background.",
|
||||||
|
"dataset_empty_tips": "You don’t have a knowledge base yet, create one first.",
|
||||||
"dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.",
|
"dataset_search_tool_description": "Call the \"Semantic Search\" and \"Full-text Search\" capabilities to find reference content that may be related to the problem from the \"Knowledge Base\". \nPrioritize calling this tool to assist in answering user questions.",
|
||||||
"dataset_select": "Optional knowledge base",
|
"dataset_select": "Optional knowledge base",
|
||||||
"day": "Day",
|
"day": "Day",
|
||||||
|
|
|
||||||
|
|
@ -783,7 +783,6 @@
|
||||||
"dataset.Manual collection Tip": "Manual datasets allow you to create an empty container to hold data",
|
"dataset.Manual collection Tip": "Manual datasets allow you to create an empty container to hold data",
|
||||||
"dataset.Move Failed": "Move Error",
|
"dataset.Move Failed": "Move Error",
|
||||||
"dataset.Select Dataset": "Select This Dataset",
|
"dataset.Select Dataset": "Select This Dataset",
|
||||||
"dataset.Select Dataset Tips": "Only Datasets with the same index model can be selected",
|
|
||||||
"dataset.Select Folder": "Enter Folder",
|
"dataset.Select Folder": "Enter Folder",
|
||||||
"dataset.Training Name": "Data Training",
|
"dataset.Training Name": "Data Training",
|
||||||
"dataset.collections.Collection Embedding": "{{total}} Indexes",
|
"dataset.collections.Collection Embedding": "{{total}} Indexes",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"Add_tool": "添加工具",
|
"Add_tool": "添加工具",
|
||||||
"AutoOptimize": "自动优化",
|
"AutoOptimize": "自动优化",
|
||||||
"Click_to_delete_this_field": "点击删除该字段",
|
"Click_to_delete_this_field": "点击删除该字段",
|
||||||
|
"Create_dataset": "创建知识库",
|
||||||
"Custom_params": "输入参数",
|
"Custom_params": "输入参数",
|
||||||
"Edit_tool": "编辑工具",
|
"Edit_tool": "编辑工具",
|
||||||
"Filed_is_deprecated": "该字段已弃用",
|
"Filed_is_deprecated": "该字段已弃用",
|
||||||
|
|
@ -129,6 +130,10 @@
|
||||||
"custom_plugin_user_guide_label": "使用说明",
|
"custom_plugin_user_guide_label": "使用说明",
|
||||||
"custom_plugin_user_guide_placeholder": "使用 markdown 语法",
|
"custom_plugin_user_guide_placeholder": "使用 markdown 语法",
|
||||||
"dataset": "知识库",
|
"dataset": "知识库",
|
||||||
|
"dataset.Select_dataset_model_tip": "仅能选择同一个索引模型的知识库",
|
||||||
|
"dataset.create_dataset_tips": "更多高级操作请前往",
|
||||||
|
"dataset_create_success": "知识库创建成功,正在后台索引文件",
|
||||||
|
"dataset_empty_tips": "你还没有知识库,先创建一个吧",
|
||||||
"dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。",
|
"dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。",
|
||||||
"dataset_select": "可选知识库",
|
"dataset_select": "可选知识库",
|
||||||
"day": "日",
|
"day": "日",
|
||||||
|
|
|
||||||
|
|
@ -786,7 +786,6 @@
|
||||||
"dataset.Manual collection Tip": "手动数据集允许创建一个空的容器装入数据",
|
"dataset.Manual collection Tip": "手动数据集允许创建一个空的容器装入数据",
|
||||||
"dataset.Move Failed": "移动出现错误~",
|
"dataset.Move Failed": "移动出现错误~",
|
||||||
"dataset.Select Dataset": "选择该知识库",
|
"dataset.Select Dataset": "选择该知识库",
|
||||||
"dataset.Select Dataset Tips": "仅能选择同一个索引模型的知识库",
|
|
||||||
"dataset.Select Folder": "进入文件夹",
|
"dataset.Select Folder": "进入文件夹",
|
||||||
"dataset.Training Name": "数据训练",
|
"dataset.Training Name": "数据训练",
|
||||||
"dataset.collections.Collection Embedding": "{{total}} 组索引中",
|
"dataset.collections.Collection Embedding": "{{total}} 组索引中",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"Add_tool": "添加工具",
|
"Add_tool": "添加工具",
|
||||||
"AutoOptimize": "自動優化",
|
"AutoOptimize": "自動優化",
|
||||||
"Click_to_delete_this_field": "點擊刪除該字段",
|
"Click_to_delete_this_field": "點擊刪除該字段",
|
||||||
|
"Create_dataset": "創建知識庫",
|
||||||
"Custom_params": "輸入參數",
|
"Custom_params": "輸入參數",
|
||||||
"Filed_is_deprecated": "該字段已棄用",
|
"Filed_is_deprecated": "該字段已棄用",
|
||||||
"HTTPTools_Create_Type": "創建方式",
|
"HTTPTools_Create_Type": "創建方式",
|
||||||
|
|
@ -125,6 +126,10 @@
|
||||||
"custom_plugin_user_guide_label": "使用說明",
|
"custom_plugin_user_guide_label": "使用說明",
|
||||||
"custom_plugin_user_guide_placeholder": "使用 markdown 語法",
|
"custom_plugin_user_guide_placeholder": "使用 markdown 語法",
|
||||||
"dataset": "知識庫",
|
"dataset": "知識庫",
|
||||||
|
"dataset.Select_dataset_model_tip": "僅能選擇同一個索引模型的知識庫",
|
||||||
|
"dataset.create_dataset_tips": "更多高級操作請前往",
|
||||||
|
"dataset_create_success": "知識庫創建成功,正在後台索引文件",
|
||||||
|
"dataset_empty_tips": "你還沒有知識庫,先創建一個吧",
|
||||||
"dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。",
|
"dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。",
|
||||||
"dataset_select": "可選知識庫",
|
"dataset_select": "可選知識庫",
|
||||||
"day": "日",
|
"day": "日",
|
||||||
|
|
|
||||||
|
|
@ -782,7 +782,6 @@
|
||||||
"dataset.Manual collection Tip": "手動資料集允許建立一個空的容器來存放資料",
|
"dataset.Manual collection Tip": "手動資料集允許建立一個空的容器來存放資料",
|
||||||
"dataset.Move Failed": "移動錯誤",
|
"dataset.Move Failed": "移動錯誤",
|
||||||
"dataset.Select Dataset": "選擇此知識庫",
|
"dataset.Select Dataset": "選擇此知識庫",
|
||||||
"dataset.Select Dataset Tips": "僅能選擇相同索引模型的知識庫",
|
|
||||||
"dataset.Select Folder": "進入資料夾",
|
"dataset.Select Folder": "進入資料夾",
|
||||||
"dataset.Training Name": "資料訓練",
|
"dataset.Training Name": "資料訓練",
|
||||||
"dataset.collections.Collection Embedding": "{{total}} 個索引",
|
"dataset.collections.Collection Embedding": "{{total}} 個索引",
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ import {
|
||||||
VStack,
|
VStack,
|
||||||
HStack,
|
HStack,
|
||||||
IconButton,
|
IconButton,
|
||||||
Spacer
|
Spacer,
|
||||||
|
useDisclosure
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { ChevronRightIcon, CloseIcon, InfoIcon } from '@chakra-ui/icons';
|
import { ChevronRightIcon, CloseIcon } from '@chakra-ui/icons';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io';
|
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type';
|
import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type';
|
||||||
|
|
@ -26,6 +27,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||||
import { useDatasetSelect } from '@/components/core/dataset/SelectModal';
|
import { useDatasetSelect } from '@/components/core/dataset/SelectModal';
|
||||||
import FolderPath from '@/components/common/folder/Path';
|
import FolderPath from '@/components/common/folder/Path';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import QuickCreateDatasetModal from '@/pageComponents/app/detail/components/QuickCreateModal';
|
||||||
|
|
||||||
// Dataset selection modal component
|
// Dataset selection modal component
|
||||||
export const DatasetSelectModal = ({
|
export const DatasetSelectModal = ({
|
||||||
|
|
@ -35,19 +37,28 @@ export const DatasetSelectModal = ({
|
||||||
onClose
|
onClose
|
||||||
}: {
|
}: {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
defaultSelectedDatasets: SelectedDatasetType;
|
defaultSelectedDatasets: SelectedDatasetType[];
|
||||||
onChange: (e: SelectedDatasetType) => void;
|
onChange: (e: SelectedDatasetType[]) => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
// Translation function
|
// Translation function
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
// Current selected datasets, initialized with defaultSelectedDatasets
|
// Current selected datasets, initialized with defaultSelectedDatasets
|
||||||
const [selectedDatasets, setSelectedDatasets] =
|
const [selectedDatasets, setSelectedDatasets] =
|
||||||
useState<SelectedDatasetType>(defaultSelectedDatasets);
|
useState<SelectedDatasetType[]>(defaultSelectedDatasets);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Use server-side search, following the logic of the dataset list page
|
// Use server-side search, following the logic of the dataset list page
|
||||||
const { paths, setParentId, searchKey, setSearchKey, datasets, isFetching } = useDatasetSelect();
|
const {
|
||||||
|
paths,
|
||||||
|
parentId,
|
||||||
|
setParentId,
|
||||||
|
searchKey,
|
||||||
|
setSearchKey,
|
||||||
|
datasets,
|
||||||
|
isFetching,
|
||||||
|
loadDatasets
|
||||||
|
} = useDatasetSelect();
|
||||||
|
|
||||||
// The vector model of the first selected dataset
|
// The vector model of the first selected dataset
|
||||||
const activeVectorModel = selectedDatasets[0]?.vectorModel?.model;
|
const activeVectorModel = selectedDatasets[0]?.vectorModel?.model;
|
||||||
|
|
@ -98,7 +109,7 @@ export const DatasetSelectModal = ({
|
||||||
if (isDatasetDisabled(item)) {
|
if (isDatasetDisabled(item)) {
|
||||||
return toast({
|
return toast({
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
title: t('common:dataset.Select Dataset Tips')
|
title: t('app:dataset.Select_dataset_model_tip')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setSelectedDatasets((prev) => [
|
setSelectedDatasets((prev) => [
|
||||||
|
|
@ -115,6 +126,15 @@ export const DatasetSelectModal = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
isOpen: isQuickCreateOpen,
|
||||||
|
onOpen: onOpenQuickCreate,
|
||||||
|
onClose: onCloseQuickCreate
|
||||||
|
} = useDisclosure();
|
||||||
|
const isRootEmpty = useMemo(() => {
|
||||||
|
return datasets.length === 0 && paths.length === 0 && !searchKey && !isFetching;
|
||||||
|
}, [datasets.length, isFetching, paths.length, searchKey]);
|
||||||
|
|
||||||
// Render component
|
// Render component
|
||||||
return (
|
return (
|
||||||
<MyModal
|
<MyModal
|
||||||
|
|
@ -124,281 +144,356 @@ export const DatasetSelectModal = ({
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
minW="800px"
|
minW="800px"
|
||||||
maxW={'800px'}
|
maxW={'800px'}
|
||||||
|
maxH={'90vh'}
|
||||||
h={'100%'}
|
h={'100%'}
|
||||||
minH={'496px'}
|
minH={'496px'}
|
||||||
maxH={'90vh'}
|
|
||||||
isCentered
|
isCentered
|
||||||
isLoading={isFetching}
|
isLoading={isFetching}
|
||||||
>
|
>
|
||||||
{/* Main vertical layout */}
|
{/* Main vertical layout */}
|
||||||
<Flex h="100%" direction="column" flex={1} overflow="hidden" minH={0}>
|
<Flex h="100%" direction="column" flex={1} overflow="hidden" minH={0}>
|
||||||
<ModalBody flex={1} overflow="hidden" minH={0}>
|
<ModalBody flex={1} h={0} overflow="hidden">
|
||||||
{/* Two-column layout */}
|
{isRootEmpty ? (
|
||||||
<Grid
|
<VStack mt={8}>
|
||||||
border="1px solid"
|
<EmptyTip text={t('app:dataset_empty_tips')} py={4} />
|
||||||
borderColor="myGray.200"
|
<Button onClick={onOpenQuickCreate}>{t('common:Create')}</Button>
|
||||||
borderRadius="md"
|
</VStack>
|
||||||
gridTemplateColumns="1fr 1fr"
|
) : (
|
||||||
h="100%"
|
<>
|
||||||
overflow="hidden"
|
{/* Two-column layout */}
|
||||||
minH={0}
|
<Grid
|
||||||
>
|
border="1px solid"
|
||||||
{/* Left: search and dataset list */}
|
borderColor="myGray.200"
|
||||||
<Flex
|
borderRadius="md"
|
||||||
h="100%"
|
gridTemplateColumns="1fr 1fr"
|
||||||
direction="column"
|
h="100%"
|
||||||
borderRight="1px solid"
|
overflow="hidden"
|
||||||
borderColor="myGray.200"
|
>
|
||||||
py={4}
|
{/* Left: search and dataset list */}
|
||||||
overflow="hidden"
|
<Flex
|
||||||
minH={0}
|
h="100%"
|
||||||
>
|
direction="column"
|
||||||
{/* Search box */}
|
borderRight="1px solid"
|
||||||
<Box mb={2} px={4}>
|
borderColor="myGray.200"
|
||||||
<SearchInput
|
py={4}
|
||||||
placeholder={t('app:Search_dataset')}
|
overflow="hidden"
|
||||||
value={searchKey}
|
>
|
||||||
onChange={(e) => setSearchKey(e.target.value?.trim())}
|
{/* Search box */}
|
||||||
size="md"
|
<Box mb={2} px={4}>
|
||||||
/>
|
<SearchInput
|
||||||
</Box>
|
placeholder={t('app:Search_dataset')}
|
||||||
|
value={searchKey}
|
||||||
{/* Path display area - always occupies space, content changes based on search state */}
|
onChange={(e) => setSearchKey(e.target.value?.trim())}
|
||||||
<Box mb={2} py={1} px={4} fontSize="sm" minH={8} display="flex" alignItems="center">
|
size="md"
|
||||||
{searchKey && (
|
/>
|
||||||
|
</Box>
|
||||||
|
{/* Path display area - always occupies space, content changes based on search state */}
|
||||||
<Box
|
<Box
|
||||||
w="100%"
|
mb={2}
|
||||||
minH={6}
|
py={1}
|
||||||
|
px={4}
|
||||||
|
fontSize="sm"
|
||||||
|
minH={8}
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
fontSize="sm"
|
|
||||||
color="myGray.500"
|
|
||||||
>
|
>
|
||||||
{t('chat:search_results')}
|
{searchKey && (
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{!searchKey && paths.length === 0 && (
|
|
||||||
// Root directory path
|
|
||||||
<Flex flex={1} alignItems="center">
|
|
||||||
<Box
|
|
||||||
fontSize={['xs', 'sm']}
|
|
||||||
py={0.5}
|
|
||||||
px={1.5}
|
|
||||||
borderRadius="sm"
|
|
||||||
maxW={['45vw', '250px']}
|
|
||||||
className="textEllipsis"
|
|
||||||
color="myGray.700"
|
|
||||||
fontWeight="bold"
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{ bg: 'myGray.100' }}
|
|
||||||
onClick={() => setParentId('')}
|
|
||||||
>
|
|
||||||
{t('common:root_folder')}
|
|
||||||
</Box>
|
|
||||||
<MyIcon name="common/line" color="myGray.500" mx={1} w="5px" />
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
{!searchKey && paths.length > 0 && (
|
|
||||||
// Subdirectory path
|
|
||||||
<FolderPath
|
|
||||||
paths={paths.map((path: ParentTreePathItemType) => ({
|
|
||||||
parentId: path.parentId,
|
|
||||||
parentName: path.parentName
|
|
||||||
}))}
|
|
||||||
FirstPathDom={t('common:root_folder')}
|
|
||||||
onClick={(e) => setParentId(e)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Dataset list */}
|
|
||||||
<VStack align="stretch" spacing={1.5} flex={1} px={4} overflowY="auto" h={0} minH={0}>
|
|
||||||
{datasets.length === 0 && !isFetching && (
|
|
||||||
<EmptyTip text={t('common:folder.empty')} />
|
|
||||||
)}
|
|
||||||
{datasets.map((item: DatasetListItemType) => (
|
|
||||||
<Box key={item._id} userSelect={'none'}>
|
|
||||||
<Flex
|
|
||||||
align="center"
|
|
||||||
pr={2}
|
|
||||||
pl={4}
|
|
||||||
py={1.5}
|
|
||||||
borderRadius="md"
|
|
||||||
_hover={{ bg: 'myGray.50' }}
|
|
||||||
cursor="pointer"
|
|
||||||
onClick={() => {
|
|
||||||
if (item.type === DatasetTypeEnum.folder) {
|
|
||||||
if (searchKey) {
|
|
||||||
setSearchKey('');
|
|
||||||
}
|
|
||||||
setParentId(item._id);
|
|
||||||
} else {
|
|
||||||
onSelect(item, !isDatasetSelected(item._id));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
w={'5'}
|
w="100%"
|
||||||
onClick={(e) => e.stopPropagation()} // Prevent parent click when clicking checkbox
|
minH={6}
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
fontSize="sm"
|
||||||
|
color="myGray.500"
|
||||||
>
|
>
|
||||||
{item.type !== DatasetTypeEnum.folder && (
|
{t('chat:search_results')}
|
||||||
<Checkbox
|
|
||||||
isChecked={isDatasetSelected(item._id)}
|
|
||||||
isDisabled={isDatasetDisabled(item)}
|
|
||||||
onChange={(e) => {
|
|
||||||
const checked = e.target.checked;
|
|
||||||
onSelect(item, checked);
|
|
||||||
}}
|
|
||||||
colorScheme="blue"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
|
)}
|
||||||
{/* Avatar */}
|
{!searchKey && paths.length === 0 && datasets.length > 0 && (
|
||||||
<Avatar src={item.avatar} w={7} h={7} borderRadius="sm" ml={3} mr={2.5} />
|
// Root directory path
|
||||||
|
<Flex flex={1} alignItems="center">
|
||||||
{/* Name and type */}
|
<Box
|
||||||
<Box flex={1} minW={0}>
|
fontSize={['xs', 'sm']}
|
||||||
<Box fontSize="sm" color={'myGray.900'} lineHeight={1}>
|
py={0.5}
|
||||||
{item.name}
|
px={1.5}
|
||||||
|
borderRadius="sm"
|
||||||
|
maxW={['45vw', '250px']}
|
||||||
|
className="textEllipsis"
|
||||||
|
color="myGray.700"
|
||||||
|
fontWeight="bold"
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{ bg: 'myGray.100' }}
|
||||||
|
onClick={() => setParentId('')}
|
||||||
|
>
|
||||||
|
{t('common:root_folder')}
|
||||||
</Box>
|
</Box>
|
||||||
<Box fontSize="xs" color="myGray.500">
|
<MyIcon name="common/line" color="myGray.500" mx={1} w="5px" />
|
||||||
{item.type === DatasetTypeEnum.folder ? (
|
</Flex>
|
||||||
<>{t('common:Folder')}</>
|
)}
|
||||||
) : (
|
{!searchKey && paths.length > 0 && (
|
||||||
<>
|
// Subdirectory path
|
||||||
{t('app:Index')}: {item.vectorModel.name}
|
<FolderPath
|
||||||
</>
|
paths={paths.map((path: ParentTreePathItemType) => ({
|
||||||
)}
|
parentId: path.parentId,
|
||||||
</Box>
|
parentName: path.parentName
|
||||||
</Box>
|
}))}
|
||||||
|
FirstPathDom={t('common:root_folder')}
|
||||||
{/* Folder expand arrow */}
|
onClick={(e) => setParentId(e)}
|
||||||
{item.type === DatasetTypeEnum.folder && (
|
/>
|
||||||
<Box mr={10}>
|
)}
|
||||||
<ChevronRightIcon w={5} h={5} color="myGray.500" strokeWidth="1px" />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
{/* Dataset list */}
|
||||||
</VStack>
|
<VStack
|
||||||
|
align="stretch"
|
||||||
{/* Select all / Deselect all */}
|
spacing={1.5}
|
||||||
{datasets.length > 0 && (
|
flex={1}
|
||||||
<Flex mt={3} px={4} justify="space-between" align="center">
|
px={4}
|
||||||
<Checkbox
|
overflowY="auto"
|
||||||
isChecked={isAllSelected}
|
h={0}
|
||||||
onChange={(e) => {
|
minH={0}
|
||||||
if (e.target.checked) {
|
|
||||||
const compatibleDatasets = compatibleDatasetsByModel.filter((dataset) => {
|
|
||||||
return !isDatasetSelected(dataset._id);
|
|
||||||
});
|
|
||||||
const newSelections = compatibleDatasets.map(
|
|
||||||
(item: DatasetListItemType) => ({
|
|
||||||
datasetId: item._id,
|
|
||||||
avatar: item.avatar,
|
|
||||||
name: item.name,
|
|
||||||
vectorModel: item.vectorModel
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setSelectedDatasets((prev) => [...prev, ...newSelections]);
|
|
||||||
} else {
|
|
||||||
const datasetIdsToRemove = compatibleDatasetsByModel.map(
|
|
||||||
(item: DatasetListItemType) => item._id
|
|
||||||
);
|
|
||||||
setSelectedDatasets((prev) =>
|
|
||||||
prev.filter((dataset) => !datasetIdsToRemove.includes(dataset.datasetId))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
colorScheme="blue"
|
|
||||||
size="sm"
|
|
||||||
>
|
>
|
||||||
<Box fontSize="sm">{t('common:Select_all')}</Box>
|
{datasets.length === 0 && !isFetching && (
|
||||||
</Checkbox>
|
<EmptyTip text={t('common:folder.empty')} />
|
||||||
|
)}
|
||||||
|
{datasets.map((item: DatasetListItemType) => (
|
||||||
|
<Box key={item._id} userSelect={'none'}>
|
||||||
|
<Flex
|
||||||
|
align="center"
|
||||||
|
pr={2}
|
||||||
|
pl={4}
|
||||||
|
py={1.5}
|
||||||
|
borderRadius="md"
|
||||||
|
_hover={{ bg: 'myGray.50' }}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() => {
|
||||||
|
if (item.type === DatasetTypeEnum.folder) {
|
||||||
|
if (searchKey) {
|
||||||
|
setSearchKey('');
|
||||||
|
}
|
||||||
|
setParentId(item._id);
|
||||||
|
} else {
|
||||||
|
onSelect(item, !isDatasetSelected(item._id));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
w={'5'}
|
||||||
|
onClick={(e) => e.stopPropagation()} // Prevent parent click when clicking checkbox
|
||||||
|
>
|
||||||
|
{item.type !== DatasetTypeEnum.folder && (
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isDatasetSelected(item._id)}
|
||||||
|
isDisabled={isDatasetDisabled(item)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const checked = e.target.checked;
|
||||||
|
onSelect(item, checked);
|
||||||
|
}}
|
||||||
|
colorScheme="blue"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Avatar */}
|
||||||
|
<Avatar src={item.avatar} w={7} h={7} borderRadius="sm" ml={3} mr={2.5} />
|
||||||
|
|
||||||
|
{/* Name and type */}
|
||||||
|
<Box flex={1} minW={0}>
|
||||||
|
<Box fontSize="sm" color={'myGray.900'} lineHeight={1}>
|
||||||
|
{item.name}
|
||||||
|
</Box>
|
||||||
|
<Box fontSize="xs" color="myGray.500">
|
||||||
|
{item.type === DatasetTypeEnum.folder ? (
|
||||||
|
<>{t('common:Folder')}</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{t('app:Index')}: {item.vectorModel.name}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Folder expand arrow */}
|
||||||
|
{item.type === DatasetTypeEnum.folder && (
|
||||||
|
<Box mr={10}>
|
||||||
|
<ChevronRightIcon w={5} h={5} color="myGray.500" strokeWidth="1px" />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
{/* Select all / Deselect all */}
|
||||||
|
{datasets.length > 0 && (
|
||||||
|
<Flex mt={3} px={4} justify="space-between" align="center">
|
||||||
|
<Checkbox
|
||||||
|
isChecked={isAllSelected}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
const compatibleDatasets = compatibleDatasetsByModel.filter(
|
||||||
|
(dataset) => {
|
||||||
|
return !isDatasetSelected(dataset._id);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const newSelections = compatibleDatasets.map(
|
||||||
|
(item: DatasetListItemType) => ({
|
||||||
|
datasetId: item._id,
|
||||||
|
avatar: item.avatar,
|
||||||
|
name: item.name,
|
||||||
|
vectorModel: item.vectorModel
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setSelectedDatasets((prev) => [...prev, ...newSelections]);
|
||||||
|
} else {
|
||||||
|
const datasetIdsToRemove = compatibleDatasetsByModel.map(
|
||||||
|
(item: DatasetListItemType) => item._id
|
||||||
|
);
|
||||||
|
setSelectedDatasets((prev) =>
|
||||||
|
prev.filter(
|
||||||
|
(dataset) => !datasetIdsToRemove.includes(dataset.datasetId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
colorScheme="blue"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Box fontSize="sm">{t('common:Select_all')}</Box>
|
||||||
|
</Checkbox>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* Right: selected datasets display */}
|
{/* Right: selected datasets display */}
|
||||||
<Flex h="100%" py={4} direction="column" overflow="hidden" minH={0}>
|
<Flex h="100%" py={4} direction="column" overflow="hidden" minH={0}>
|
||||||
{/* Selected count display */}
|
{!isRootEmpty && (
|
||||||
<Box mb={3} px={4} fontSize="sm" color="myGray.600">
|
<>
|
||||||
{t('app:Selected')}: {selectedDatasets.length} {t('app:dataset')}
|
{/* Selected count display */}
|
||||||
</Box>
|
<Box mb={3} px={4} fontSize="sm" color="myGray.600">
|
||||||
{/* Selected dataset list */}
|
{t('app:Selected')}: {selectedDatasets.length} {t('app:dataset')}
|
||||||
<VStack align="stretch" spacing={1} flex={1} px={4} overflowY="auto" h={0} minH={0}>
|
</Box>
|
||||||
{selectedDatasets.length === 0 && !isFetching && (
|
{/* Selected dataset list */}
|
||||||
<EmptyTip text={t('app:No_selected_dataset')} />
|
<VStack
|
||||||
)}
|
align={'stretch'}
|
||||||
{selectedDatasets.map((item) => (
|
overflowY={'auto'}
|
||||||
<Flex
|
spacing={1}
|
||||||
key={item.datasetId}
|
flex={1}
|
||||||
px={2}
|
px={4}
|
||||||
py={1.5}
|
h={0}
|
||||||
borderRadius="md"
|
minH={0}
|
||||||
_hover={{ bg: 'myGray.50' }}
|
>
|
||||||
cursor="pointer"
|
{selectedDatasets.length === 0 && !isFetching && (
|
||||||
alignItems="center"
|
<EmptyTip text={t('app:No_selected_dataset')} />
|
||||||
>
|
)}
|
||||||
<Avatar src={item.avatar} w={6} h={6} borderRadius="sm" mr={3} />
|
{selectedDatasets.map((item) => (
|
||||||
<Box flex={1} minW={0}>
|
<Flex
|
||||||
<Box fontSize="sm">{item.name}</Box>
|
key={item.datasetId}
|
||||||
</Box>
|
px={2}
|
||||||
<IconButton
|
py={1.5}
|
||||||
aria-label="Remove"
|
borderRadius="md"
|
||||||
icon={<CloseIcon w={2.5} h={2.5} />}
|
_hover={{ bg: 'myGray.50' }}
|
||||||
size="xs"
|
cursor="pointer"
|
||||||
variant="ghost"
|
alignItems="center"
|
||||||
color="black"
|
>
|
||||||
_hover={{ bg: 'myGray.200' }}
|
<Avatar src={item.avatar} w={6} h={6} borderRadius="sm" mr={3} />
|
||||||
onClick={() =>
|
<Box flex={1} minW={0}>
|
||||||
setSelectedDatasets((prev) =>
|
<Box fontSize="sm">{item.name}</Box>
|
||||||
prev.filter((dataset) => dataset.datasetId !== item.datasetId)
|
</Box>
|
||||||
)
|
<IconButton
|
||||||
}
|
aria-label="Remove"
|
||||||
/>
|
icon={<CloseIcon w={2.5} h={2.5} />}
|
||||||
</Flex>
|
size="xs"
|
||||||
))}
|
variant="ghost"
|
||||||
</VStack>
|
color="black"
|
||||||
</Flex>
|
_hover={{ bg: 'myGray.200' }}
|
||||||
</Grid>
|
onClick={() =>
|
||||||
|
setSelectedDatasets((prev) =>
|
||||||
|
prev.filter((dataset) => dataset.datasetId !== item.datasetId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
{/* Modal footer button area */}
|
{/* Modal footer button area */}
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<HStack spacing={4} w="full" align="center">
|
<HStack spacing={4} w="full" align="center">
|
||||||
<Spacer />
|
{!isRootEmpty && (
|
||||||
<HStack spacing={3} align="center">
|
|
||||||
<Box
|
|
||||||
px={3}
|
|
||||||
py={2}
|
|
||||||
borderRadius="md"
|
|
||||||
bg="blue.50"
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
fontSize="xs"
|
|
||||||
color="blue.600"
|
|
||||||
>
|
|
||||||
<InfoIcon w={3.5} h={3.5} color="blue.500" mr={2} />
|
|
||||||
{t('common:dataset.Select Dataset Tips')}
|
|
||||||
</Box>
|
|
||||||
<Button
|
<Button
|
||||||
colorScheme="blue"
|
leftIcon={<MyIcon name="common/addLight" w={4} />}
|
||||||
|
variant={'transparentBase'}
|
||||||
|
color={'primary.700'}
|
||||||
|
fontSize={'mini'}
|
||||||
|
onClick={onOpenQuickCreate}
|
||||||
|
>
|
||||||
|
{t('common:new_create')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Spacer />
|
||||||
|
{isRootEmpty ? (
|
||||||
|
<Button
|
||||||
|
px={3.5}
|
||||||
|
maxH={8}
|
||||||
|
fontSize={'mini'}
|
||||||
|
variant={'grayBase'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// Close modal and return selected datasets
|
|
||||||
onClose();
|
onClose();
|
||||||
onChange(selectedDatasets);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('common:Confirm')}
|
{t('common:Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
) : (
|
||||||
|
<HStack spacing={3} align="center">
|
||||||
|
<Flex
|
||||||
|
px={3}
|
||||||
|
py={1.5}
|
||||||
|
borderRadius={'sm'}
|
||||||
|
bg={'primary.50'}
|
||||||
|
alignItems={'center'}
|
||||||
|
fontSize={'11px'}
|
||||||
|
color={'primary.600'}
|
||||||
|
gap={1}
|
||||||
|
>
|
||||||
|
<MyIcon name={'common/info'} w={3.5} />
|
||||||
|
{t('app:dataset.Select_dataset_model_tip')}
|
||||||
|
</Flex>
|
||||||
|
<Button
|
||||||
|
px={3.5}
|
||||||
|
maxH={8}
|
||||||
|
fontSize={'mini'}
|
||||||
|
onClick={() => {
|
||||||
|
// Close modal and return selected datasets
|
||||||
|
onClose();
|
||||||
|
onChange(selectedDatasets);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common:Confirm')}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
|
{isQuickCreateOpen && (
|
||||||
|
<QuickCreateDatasetModal
|
||||||
|
parentId={parentId}
|
||||||
|
onClose={onCloseQuickCreate}
|
||||||
|
onSuccess={(newDataset) => {
|
||||||
|
setSelectedDatasets((prev) => [...prev, newDataset]);
|
||||||
|
loadDatasets();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</MyModal>
|
</MyModal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,8 @@ export function useDatasetSelect() {
|
||||||
datasets: [],
|
datasets: [],
|
||||||
paths: []
|
paths: []
|
||||||
},
|
},
|
||||||
loading: isFetching
|
loading: isFetching,
|
||||||
|
runAsync: loadDatasets
|
||||||
} = useRequest2(
|
} = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
|
|
@ -105,7 +106,8 @@ export function useDatasetSelect() {
|
||||||
setSearchKey,
|
setSearchKey,
|
||||||
datasets: data.datasets,
|
datasets: data.datasets,
|
||||||
paths: data.paths,
|
paths: data.paths,
|
||||||
isFetching
|
isFetching,
|
||||||
|
loadDatasets
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||||
} = useDisclosure();
|
} = useDisclosure();
|
||||||
|
|
||||||
const selectedDatasets = useMemo(() => {
|
const selectedDatasets = useMemo(() => {
|
||||||
if (Array.isArray(item.value)) return item.value as SelectedDatasetType;
|
if (Array.isArray(item.value)) return item.value as SelectedDatasetType[];
|
||||||
return [] as SelectedDatasetType;
|
return [] as SelectedDatasetType[];
|
||||||
}, [item.value]);
|
}, [item.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useForm } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Flex,
|
||||||
|
ModalBody,
|
||||||
|
ModalFooter,
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Progress
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||||
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
|
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import { useUploadAvatar } from '@fastgpt/web/common/file/hooks/useUploadAvatar';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import {
|
||||||
|
postCreateDataset,
|
||||||
|
getDatasetById,
|
||||||
|
postCreateDatasetFileCollection
|
||||||
|
} from '@/web/core/dataset/api';
|
||||||
|
import { getUploadAvatarPresignedUrl } from '@/web/common/file/api';
|
||||||
|
import { uploadFile2DB } from '@/web/common/file/controller';
|
||||||
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
|
import {
|
||||||
|
ChunkSettingModeEnum,
|
||||||
|
ChunkTriggerConfigTypeEnum,
|
||||||
|
DataChunkSplitModeEnum,
|
||||||
|
DatasetCollectionDataProcessModeEnum,
|
||||||
|
DatasetTypeEnum
|
||||||
|
} from '@fastgpt/global/core/dataset/constants';
|
||||||
|
import { getWebDefaultEmbeddingModel, getWebDefaultLLMModel } from '@/web/common/system/utils';
|
||||||
|
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||||
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
|
import { formatFileSize } from '@fastgpt/global/common/file/tools';
|
||||||
|
import { getFileIcon } from '@fastgpt/global/common/file/icon';
|
||||||
|
import type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io';
|
||||||
|
import type { ImportSourceItemType } from '@/web/core/dataset/type';
|
||||||
|
import FileSelector, {
|
||||||
|
type SelectFileItemType
|
||||||
|
} from '@/pageComponents/dataset/detail/Import/components/FileSelector';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
|
||||||
|
const QuickCreateDatasetModal = ({
|
||||||
|
onClose,
|
||||||
|
onSuccess,
|
||||||
|
parentId
|
||||||
|
}: {
|
||||||
|
onClose: () => void;
|
||||||
|
onSuccess: (dataset: SelectedDatasetType) => void;
|
||||||
|
parentId: string;
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const router = useRouter();
|
||||||
|
const { defaultModels, embeddingModelList, datasetModelList } = useSystemStore();
|
||||||
|
|
||||||
|
const defaultVectorModel =
|
||||||
|
defaultModels.embedding?.model || getWebDefaultEmbeddingModel(embeddingModelList)?.model;
|
||||||
|
const defaultAgentModel =
|
||||||
|
defaultModels.datasetTextLLM?.model || getWebDefaultLLMModel(datasetModelList)?.model;
|
||||||
|
const defaultVLLM = defaultModels.datasetImageLLM?.model;
|
||||||
|
|
||||||
|
const [selectFiles, setSelectFiles] = useState<ImportSourceItemType[]>([]);
|
||||||
|
|
||||||
|
const { register, handleSubmit, watch, setValue } = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
parentId,
|
||||||
|
name: '',
|
||||||
|
avatar: 'core/dataset/commonDatasetColor'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const avatar = watch('avatar');
|
||||||
|
|
||||||
|
const { Component: AvatarUploader, handleFileSelectorOpen: handleAvatarSelectorOpen } =
|
||||||
|
useUploadAvatar(getUploadAvatarPresignedUrl, {
|
||||||
|
onSuccess: (avatarUrl: string) => {
|
||||||
|
setValue('avatar', avatarUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectFiles = (files: SelectFileItemType[]) => {
|
||||||
|
setSelectFiles((state) => [
|
||||||
|
...state,
|
||||||
|
...files.map<ImportSourceItemType>((selectFile) => {
|
||||||
|
const { fileId, file } = selectFile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: fileId,
|
||||||
|
createStatus: 'waiting',
|
||||||
|
file,
|
||||||
|
sourceName: file.name,
|
||||||
|
sourceSize: formatFileSize(file.size),
|
||||||
|
icon: getFileIcon(file.name),
|
||||||
|
uploadedFileRate: 0
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadSingleFile = async (fileItem: ImportSourceItemType, datasetId: string) => {
|
||||||
|
try {
|
||||||
|
if (!fileItem.file) return;
|
||||||
|
setSelectFiles((prev) =>
|
||||||
|
prev.map((item) => (item.id === fileItem.id ? { ...item, uploadedFileRate: 0 } : item))
|
||||||
|
);
|
||||||
|
|
||||||
|
const { fileId } = await uploadFile2DB({
|
||||||
|
file: fileItem.file,
|
||||||
|
bucketName: BucketNameEnum.dataset,
|
||||||
|
data: { datasetId },
|
||||||
|
percentListen: (percent) => {
|
||||||
|
setSelectFiles((prev) =>
|
||||||
|
prev.map((item) =>
|
||||||
|
item.id === fileItem.id
|
||||||
|
? { ...item, uploadedFileRate: Math.max(percent, item.uploadedFileRate || 0) }
|
||||||
|
: item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await postCreateDatasetFileCollection({
|
||||||
|
datasetId,
|
||||||
|
fileId,
|
||||||
|
trainingType: DatasetCollectionDataProcessModeEnum.chunk,
|
||||||
|
chunkTriggerType: ChunkTriggerConfigTypeEnum.minSize,
|
||||||
|
chunkTriggerMinSize: 1000,
|
||||||
|
chunkSettingMode: ChunkSettingModeEnum.auto,
|
||||||
|
chunkSplitMode: DataChunkSplitModeEnum.paragraph,
|
||||||
|
chunkSize: 1024,
|
||||||
|
indexSize: 512,
|
||||||
|
customPdfParse: false
|
||||||
|
});
|
||||||
|
|
||||||
|
setSelectFiles((prev) =>
|
||||||
|
prev.map((item) =>
|
||||||
|
item.id === fileItem.id ? { ...item, dbFileId: fileId, uploadedFileRate: 100 } : item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
setSelectFiles((prev) =>
|
||||||
|
prev.map((item) =>
|
||||||
|
item.id === fileItem.id ? { ...item, errorMsg: getErrText(error) } : item
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { runAsync: onCreate, loading: isCreating } = useRequest2(
|
||||||
|
async (data) => {
|
||||||
|
const datasetId = await postCreateDataset({
|
||||||
|
name: data.name.trim(),
|
||||||
|
avatar: data.avatar,
|
||||||
|
intro: '',
|
||||||
|
parentId,
|
||||||
|
type: DatasetTypeEnum.dataset,
|
||||||
|
vectorModel: defaultVectorModel,
|
||||||
|
agentModel: defaultAgentModel,
|
||||||
|
vlmModel: defaultVLLM
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectFiles.length > 0) {
|
||||||
|
await Promise.all(selectFiles.map((file) => uploadSingleFile(file, datasetId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const datasetDetail = await getDatasetById(datasetId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
datasetId,
|
||||||
|
name: datasetDetail.name,
|
||||||
|
avatar: datasetDetail.avatar,
|
||||||
|
vectorModel: datasetDetail.vectorModel
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
manual: true,
|
||||||
|
successToast: t('app:dataset_create_success'),
|
||||||
|
onSuccess: (result) => {
|
||||||
|
onSuccess(result);
|
||||||
|
onClose();
|
||||||
|
setSelectFiles([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isOpen={true}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t('app:Create_dataset')}
|
||||||
|
minW={'800px'}
|
||||||
|
ml={'20px'}
|
||||||
|
>
|
||||||
|
<ModalBody py={6} minH={'500px'}>
|
||||||
|
<Box mb={6}>
|
||||||
|
<FormLabel mb={2}>{t('common:app_icon_and_name')}</FormLabel>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<MyTooltip label={t('common:set_avatar')}>
|
||||||
|
<Avatar
|
||||||
|
src={avatar}
|
||||||
|
w={9}
|
||||||
|
h={9}
|
||||||
|
mr={4}
|
||||||
|
borderRadius={'8px'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
onClick={handleAvatarSelectorOpen}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
<FormControl flex={1}>
|
||||||
|
<Input
|
||||||
|
{...register('name', { required: true })}
|
||||||
|
placeholder={t('common:dataset.dataset_name')}
|
||||||
|
h={8}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<FileSelector
|
||||||
|
fileType={'.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .htm, .pptx, .doc, .xls, .ppt'}
|
||||||
|
selectFiles={selectFiles}
|
||||||
|
onSelectFiles={handleSelectFiles}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectFiles.length > 0 && (
|
||||||
|
<Flex mt={6} flexDirection={'column'} gap={1.5}>
|
||||||
|
{selectFiles.map((item) => (
|
||||||
|
<Flex
|
||||||
|
key={item.id}
|
||||||
|
px={3}
|
||||||
|
py={1.5}
|
||||||
|
h={9}
|
||||||
|
alignItems={'center'}
|
||||||
|
borderRadius={'8px'}
|
||||||
|
boxShadow={
|
||||||
|
'0 1px 2px 0 rgba(19, 51, 107, 0.05), 0 0 1px 0 rgba(19, 51, 107, 0.08)'
|
||||||
|
}
|
||||||
|
gap={2}
|
||||||
|
>
|
||||||
|
<MyIcon name={item.icon as any} w={5} />
|
||||||
|
<Flex
|
||||||
|
alignItems={'center'}
|
||||||
|
w={2 / 5}
|
||||||
|
whiteSpace={'nowrap'}
|
||||||
|
overflow={'hidden'}
|
||||||
|
textOverflow={'ellipsis'}
|
||||||
|
fontSize={'14px'}
|
||||||
|
color={'myGray.900'}
|
||||||
|
mr={4}
|
||||||
|
flexShrink={0}
|
||||||
|
>
|
||||||
|
{item.sourceName}
|
||||||
|
</Flex>
|
||||||
|
<Flex w={2 / 5} pl={2} flexShrink={0}>
|
||||||
|
{item.errorMsg ? (
|
||||||
|
<MyTooltip label={item.errorMsg}>
|
||||||
|
<Flex alignItems={'center'} color={'red.500'}>
|
||||||
|
<Box mr={1} fontSize={'sm'}>
|
||||||
|
{t('common:Error')}
|
||||||
|
</Box>
|
||||||
|
<MyIcon name={'help'} w={4} />
|
||||||
|
</Flex>
|
||||||
|
</MyTooltip>
|
||||||
|
) : !!item.uploadedFileRate ? (
|
||||||
|
<Flex alignItems={'center'} fontSize={'xs'} w={'full'}>
|
||||||
|
<Progress
|
||||||
|
value={item.uploadedFileRate}
|
||||||
|
h={'4px'}
|
||||||
|
w={'100%'}
|
||||||
|
maxW={'210px'}
|
||||||
|
size="sm"
|
||||||
|
borderRadius={'20px'}
|
||||||
|
colorScheme={item.uploadedFileRate === 100 ? 'green' : 'blue'}
|
||||||
|
bg="myGray.200"
|
||||||
|
hasStripe
|
||||||
|
isAnimated
|
||||||
|
mr={4}
|
||||||
|
/>
|
||||||
|
{`${item.uploadedFileRate}%`}
|
||||||
|
</Flex>
|
||||||
|
) : null}
|
||||||
|
</Flex>
|
||||||
|
<Flex w={1 / 5} justifyContent={'end'}>
|
||||||
|
{!item.uploadedFileRate && (
|
||||||
|
<Flex alignItems={'center'} justifyContent={'center'} w={6} h={6}>
|
||||||
|
<MyIcon
|
||||||
|
name={'delete'}
|
||||||
|
w={4}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{ color: 'red.500' }}
|
||||||
|
onClick={() =>
|
||||||
|
setSelectFiles((prev) =>
|
||||||
|
prev.filter((prevItem) => prevItem.id !== item.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter justifyContent={'space-between'} fontSize={'14px'}>
|
||||||
|
<Flex fontWeight={'medium'}>
|
||||||
|
<Box color={'myGray.500'}>{t('app:dataset.create_dataset_tips')}</Box>
|
||||||
|
<Box
|
||||||
|
px={1}
|
||||||
|
cursor={'pointer'}
|
||||||
|
color={'primary.600'}
|
||||||
|
onClick={() => {
|
||||||
|
router.push('/dataset/list');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('common:core.dataset.Dataset')}
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
<Flex gap={3}>
|
||||||
|
<Button variant={'whiteBase'} onClick={onClose}>
|
||||||
|
{t('common:Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
isLoading={isCreating}
|
||||||
|
isDisabled={selectFiles.length === 0}
|
||||||
|
onClick={handleSubmit(onCreate)}
|
||||||
|
>
|
||||||
|
{t('common:Create')}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</ModalFooter>
|
||||||
|
|
||||||
|
<AvatarUploader />
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QuickCreateDatasetModal;
|
||||||
|
|
@ -20,7 +20,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { TabEnum } from '../../../../../pages/dataset/detail/index';
|
|
||||||
import {
|
import {
|
||||||
postCreateDatasetApiDatasetCollection,
|
postCreateDatasetApiDatasetCollection,
|
||||||
postCreateDatasetExternalFileCollection,
|
postCreateDatasetExternalFileCollection,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ const requestLLMPargraph = async ({
|
||||||
rawText: string;
|
rawText: string;
|
||||||
model: string;
|
model: string;
|
||||||
billId: string;
|
billId: string;
|
||||||
paragraphChunkAIMode: ParagraphChunkAIModeEnum;
|
paragraphChunkAIMode?: ParagraphChunkAIModeEnum;
|
||||||
}) => {
|
}) => {
|
||||||
if (
|
if (
|
||||||
!global.feConfigs?.isPlus ||
|
!global.feConfigs?.isPlus ||
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue