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] 应用评测
|
||||
- [ ] 高级编排 DeBug 调试模式
|
||||
- [ ] 应用节点日志
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ description: 'FastGPT V4.14.2 更新说明'
|
|||
|
||||
1. 封装底层 Agent Call 方式,支持工具连续调用时上下文的压缩,以及单个工具长响应的压缩。
|
||||
2. 模板市场新 UI。
|
||||
3. 支持 Agent 编辑页快速创建知识库。
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
|
|
@ -19,3 +20,16 @@ description: 'FastGPT V4.14.2 更新说明'
|
|||
|
||||
1. 简易应用模板未正常转化。
|
||||
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-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/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/41.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;
|
||||
};
|
||||
dataset: {
|
||||
datasets: SelectedDatasetType;
|
||||
datasets: SelectedDatasetType[];
|
||||
} & AppDatasetSearchParamsType;
|
||||
selectedTools: FlowNodeTemplateType[];
|
||||
chatConfig: AppChatConfigType;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ import type {
|
|||
SearchScoreTypeEnum,
|
||||
TrainingModeEnum,
|
||||
ChunkSettingModeEnum,
|
||||
ChunkTriggerConfigTypeEnum
|
||||
ChunkTriggerConfigTypeEnum,
|
||||
ParagraphChunkAIModeEnum
|
||||
} from './constants';
|
||||
import type { DatasetPermission } from '../../support/permission/dataset/controller';
|
||||
import type {
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ export type SelectedDatasetType = {
|
|||
avatar: string;
|
||||
name: string;
|
||||
vectorModel: EmbeddingModelItemType;
|
||||
}[];
|
||||
};
|
||||
|
||||
/* http node */
|
||||
export type HttpParamAndHeaderItemType = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { getDatasetSearchToolResponsePrompt } from '../../../../../global/core/a
|
|||
import { getNodeErrResponse } from '../utils';
|
||||
|
||||
type DatasetSearchProps = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType;
|
||||
[NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType[];
|
||||
[NodeInputKeyEnum.datasetSimilarity]: number;
|
||||
[NodeInputKeyEnum.datasetMaxTokens]: number;
|
||||
[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}
|
||||
</Box>
|
||||
<Box flex={1} />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"Add_tool": "Add tool",
|
||||
"AutoOptimize": "Automatic optimization",
|
||||
"Click_to_delete_this_field": "Click to delete this field",
|
||||
"Create_dataset": "Create a knowledge base",
|
||||
"Custom_params": "input parameters",
|
||||
"Edit_tool": "Edit tool",
|
||||
"Filed_is_deprecated": "This field is deprecated",
|
||||
|
|
@ -126,6 +127,10 @@
|
|||
"custom_plugin_user_guide_label": "User Guide",
|
||||
"custom_plugin_user_guide_placeholder": "Use markdown syntax",
|
||||
"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_select": "Optional knowledge base",
|
||||
"day": "Day",
|
||||
|
|
|
|||
|
|
@ -783,7 +783,6 @@
|
|||
"dataset.Manual collection Tip": "Manual datasets allow you to create an empty container to hold data",
|
||||
"dataset.Move Failed": "Move Error",
|
||||
"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.Training Name": "Data Training",
|
||||
"dataset.collections.Collection Embedding": "{{total}} Indexes",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"Add_tool": "添加工具",
|
||||
"AutoOptimize": "自动优化",
|
||||
"Click_to_delete_this_field": "点击删除该字段",
|
||||
"Create_dataset": "创建知识库",
|
||||
"Custom_params": "输入参数",
|
||||
"Edit_tool": "编辑工具",
|
||||
"Filed_is_deprecated": "该字段已弃用",
|
||||
|
|
@ -129,6 +130,10 @@
|
|||
"custom_plugin_user_guide_label": "使用说明",
|
||||
"custom_plugin_user_guide_placeholder": "使用 markdown 语法",
|
||||
"dataset": "知识库",
|
||||
"dataset.Select_dataset_model_tip": "仅能选择同一个索引模型的知识库",
|
||||
"dataset.create_dataset_tips": "更多高级操作请前往",
|
||||
"dataset_create_success": "知识库创建成功,正在后台索引文件",
|
||||
"dataset_empty_tips": "你还没有知识库,先创建一个吧",
|
||||
"dataset_search_tool_description": "调用“语义检索”和“全文检索”能力,从“知识库”中查找可能与问题相关的参考内容。优先调用该工具来辅助回答用户的问题。",
|
||||
"dataset_select": "可选知识库",
|
||||
"day": "日",
|
||||
|
|
|
|||
|
|
@ -786,7 +786,6 @@
|
|||
"dataset.Manual collection Tip": "手动数据集允许创建一个空的容器装入数据",
|
||||
"dataset.Move Failed": "移动出现错误~",
|
||||
"dataset.Select Dataset": "选择该知识库",
|
||||
"dataset.Select Dataset Tips": "仅能选择同一个索引模型的知识库",
|
||||
"dataset.Select Folder": "进入文件夹",
|
||||
"dataset.Training Name": "数据训练",
|
||||
"dataset.collections.Collection Embedding": "{{total}} 组索引中",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"Add_tool": "添加工具",
|
||||
"AutoOptimize": "自動優化",
|
||||
"Click_to_delete_this_field": "點擊刪除該字段",
|
||||
"Create_dataset": "創建知識庫",
|
||||
"Custom_params": "輸入參數",
|
||||
"Filed_is_deprecated": "該字段已棄用",
|
||||
"HTTPTools_Create_Type": "創建方式",
|
||||
|
|
@ -125,6 +126,10 @@
|
|||
"custom_plugin_user_guide_label": "使用說明",
|
||||
"custom_plugin_user_guide_placeholder": "使用 markdown 語法",
|
||||
"dataset": "知識庫",
|
||||
"dataset.Select_dataset_model_tip": "僅能選擇同一個索引模型的知識庫",
|
||||
"dataset.create_dataset_tips": "更多高級操作請前往",
|
||||
"dataset_create_success": "知識庫創建成功,正在後台索引文件",
|
||||
"dataset_empty_tips": "你還沒有知識庫,先創建一個吧",
|
||||
"dataset_search_tool_description": "呼叫「語意搜尋」和「全文搜尋」功能,從「知識庫」中尋找可能與問題相關的參考內容。優先呼叫這個工具來協助回答使用者的問題。",
|
||||
"dataset_select": "可選知識庫",
|
||||
"day": "日",
|
||||
|
|
|
|||
|
|
@ -782,7 +782,6 @@
|
|||
"dataset.Manual collection Tip": "手動資料集允許建立一個空的容器來存放資料",
|
||||
"dataset.Move Failed": "移動錯誤",
|
||||
"dataset.Select Dataset": "選擇此知識庫",
|
||||
"dataset.Select Dataset Tips": "僅能選擇相同索引模型的知識庫",
|
||||
"dataset.Select Folder": "進入資料夾",
|
||||
"dataset.Training Name": "資料訓練",
|
||||
"dataset.collections.Collection Embedding": "{{total}} 個索引",
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ import {
|
|||
VStack,
|
||||
HStack,
|
||||
IconButton,
|
||||
Spacer
|
||||
Spacer,
|
||||
useDisclosure
|
||||
} 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 type { SelectedDatasetType } from '@fastgpt/global/core/workflow/type/io';
|
||||
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 FolderPath from '@/components/common/folder/Path';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import QuickCreateDatasetModal from '@/pageComponents/app/detail/components/QuickCreateModal';
|
||||
|
||||
// Dataset selection modal component
|
||||
export const DatasetSelectModal = ({
|
||||
|
|
@ -35,19 +37,28 @@ export const DatasetSelectModal = ({
|
|||
onClose
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
defaultSelectedDatasets: SelectedDatasetType;
|
||||
onChange: (e: SelectedDatasetType) => void;
|
||||
defaultSelectedDatasets: SelectedDatasetType[];
|
||||
onChange: (e: SelectedDatasetType[]) => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
// Translation function
|
||||
const { t } = useTranslation();
|
||||
// Current selected datasets, initialized with defaultSelectedDatasets
|
||||
const [selectedDatasets, setSelectedDatasets] =
|
||||
useState<SelectedDatasetType>(defaultSelectedDatasets);
|
||||
useState<SelectedDatasetType[]>(defaultSelectedDatasets);
|
||||
const { toast } = useToast();
|
||||
|
||||
// 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
|
||||
const activeVectorModel = selectedDatasets[0]?.vectorModel?.model;
|
||||
|
|
@ -98,7 +109,7 @@ export const DatasetSelectModal = ({
|
|||
if (isDatasetDisabled(item)) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:dataset.Select Dataset Tips')
|
||||
title: t('app:dataset.Select_dataset_model_tip')
|
||||
});
|
||||
}
|
||||
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
|
||||
return (
|
||||
<MyModal
|
||||
|
|
@ -124,281 +144,356 @@ export const DatasetSelectModal = ({
|
|||
onClose={onClose}
|
||||
minW="800px"
|
||||
maxW={'800px'}
|
||||
maxH={'90vh'}
|
||||
h={'100%'}
|
||||
minH={'496px'}
|
||||
maxH={'90vh'}
|
||||
isCentered
|
||||
isLoading={isFetching}
|
||||
>
|
||||
{/* Main vertical layout */}
|
||||
<Flex h="100%" direction="column" flex={1} overflow="hidden" minH={0}>
|
||||
<ModalBody flex={1} overflow="hidden" minH={0}>
|
||||
{/* Two-column layout */}
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="md"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h="100%"
|
||||
overflow="hidden"
|
||||
minH={0}
|
||||
>
|
||||
{/* Left: search and dataset list */}
|
||||
<Flex
|
||||
h="100%"
|
||||
direction="column"
|
||||
borderRight="1px solid"
|
||||
borderColor="myGray.200"
|
||||
py={4}
|
||||
overflow="hidden"
|
||||
minH={0}
|
||||
>
|
||||
{/* Search box */}
|
||||
<Box mb={2} px={4}>
|
||||
<SearchInput
|
||||
placeholder={t('app:Search_dataset')}
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value?.trim())}
|
||||
size="md"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Path display area - always occupies space, content changes based on search state */}
|
||||
<Box mb={2} py={1} px={4} fontSize="sm" minH={8} display="flex" alignItems="center">
|
||||
{searchKey && (
|
||||
<ModalBody flex={1} h={0} overflow="hidden">
|
||||
{isRootEmpty ? (
|
||||
<VStack mt={8}>
|
||||
<EmptyTip text={t('app:dataset_empty_tips')} py={4} />
|
||||
<Button onClick={onOpenQuickCreate}>{t('common:Create')}</Button>
|
||||
</VStack>
|
||||
) : (
|
||||
<>
|
||||
{/* Two-column layout */}
|
||||
<Grid
|
||||
border="1px solid"
|
||||
borderColor="myGray.200"
|
||||
borderRadius="md"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
h="100%"
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* Left: search and dataset list */}
|
||||
<Flex
|
||||
h="100%"
|
||||
direction="column"
|
||||
borderRight="1px solid"
|
||||
borderColor="myGray.200"
|
||||
py={4}
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* Search box */}
|
||||
<Box mb={2} px={4}>
|
||||
<SearchInput
|
||||
placeholder={t('app:Search_dataset')}
|
||||
value={searchKey}
|
||||
onChange={(e) => setSearchKey(e.target.value?.trim())}
|
||||
size="md"
|
||||
/>
|
||||
</Box>
|
||||
{/* Path display area - always occupies space, content changes based on search state */}
|
||||
<Box
|
||||
w="100%"
|
||||
minH={6}
|
||||
mb={2}
|
||||
py={1}
|
||||
px={4}
|
||||
fontSize="sm"
|
||||
minH={8}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
fontSize="sm"
|
||||
color="myGray.500"
|
||||
>
|
||||
{t('chat:search_results')}
|
||||
</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));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{searchKey && (
|
||||
<Box
|
||||
w={'5'}
|
||||
onClick={(e) => e.stopPropagation()} // Prevent parent click when clicking checkbox
|
||||
w="100%"
|
||||
minH={6}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
fontSize="sm"
|
||||
color="myGray.500"
|
||||
>
|
||||
{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"
|
||||
/>
|
||||
)}
|
||||
{t('chat:search_results')}
|
||||
</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}
|
||||
)}
|
||||
{!searchKey && paths.length === 0 && datasets.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>
|
||||
<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>
|
||||
<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>
|
||||
))}
|
||||
</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"
|
||||
{/* Dataset list */}
|
||||
<VStack
|
||||
align="stretch"
|
||||
spacing={1.5}
|
||||
flex={1}
|
||||
px={4}
|
||||
overflowY="auto"
|
||||
h={0}
|
||||
minH={0}
|
||||
>
|
||||
<Box fontSize="sm">{t('common:Select_all')}</Box>
|
||||
</Checkbox>
|
||||
{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
|
||||
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>
|
||||
|
||||
{/* Right: selected datasets display */}
|
||||
<Flex h="100%" py={4} direction="column" overflow="hidden" minH={0}>
|
||||
{/* Selected count display */}
|
||||
<Box mb={3} px={4} fontSize="sm" color="myGray.600">
|
||||
{t('app:Selected')}: {selectedDatasets.length} {t('app:dataset')}
|
||||
</Box>
|
||||
{/* Selected dataset list */}
|
||||
<VStack align="stretch" spacing={1} flex={1} px={4} overflowY="auto" h={0} minH={0}>
|
||||
{selectedDatasets.length === 0 && !isFetching && (
|
||||
<EmptyTip text={t('app:No_selected_dataset')} />
|
||||
)}
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item.datasetId}
|
||||
px={2}
|
||||
py={1.5}
|
||||
borderRadius="md"
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
cursor="pointer"
|
||||
alignItems="center"
|
||||
>
|
||||
<Avatar src={item.avatar} w={6} h={6} borderRadius="sm" mr={3} />
|
||||
<Box flex={1} minW={0}>
|
||||
<Box fontSize="sm">{item.name}</Box>
|
||||
</Box>
|
||||
<IconButton
|
||||
aria-label="Remove"
|
||||
icon={<CloseIcon w={2.5} h={2.5} />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
color="black"
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() =>
|
||||
setSelectedDatasets((prev) =>
|
||||
prev.filter((dataset) => dataset.datasetId !== item.datasetId)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</VStack>
|
||||
</Flex>
|
||||
</Grid>
|
||||
{/* Right: selected datasets display */}
|
||||
<Flex h="100%" py={4} direction="column" overflow="hidden" minH={0}>
|
||||
{!isRootEmpty && (
|
||||
<>
|
||||
{/* Selected count display */}
|
||||
<Box mb={3} px={4} fontSize="sm" color="myGray.600">
|
||||
{t('app:Selected')}: {selectedDatasets.length} {t('app:dataset')}
|
||||
</Box>
|
||||
{/* Selected dataset list */}
|
||||
<VStack
|
||||
align={'stretch'}
|
||||
overflowY={'auto'}
|
||||
spacing={1}
|
||||
flex={1}
|
||||
px={4}
|
||||
h={0}
|
||||
minH={0}
|
||||
>
|
||||
{selectedDatasets.length === 0 && !isFetching && (
|
||||
<EmptyTip text={t('app:No_selected_dataset')} />
|
||||
)}
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item.datasetId}
|
||||
px={2}
|
||||
py={1.5}
|
||||
borderRadius="md"
|
||||
_hover={{ bg: 'myGray.50' }}
|
||||
cursor="pointer"
|
||||
alignItems="center"
|
||||
>
|
||||
<Avatar src={item.avatar} w={6} h={6} borderRadius="sm" mr={3} />
|
||||
<Box flex={1} minW={0}>
|
||||
<Box fontSize="sm">{item.name}</Box>
|
||||
</Box>
|
||||
<IconButton
|
||||
aria-label="Remove"
|
||||
icon={<CloseIcon w={2.5} h={2.5} />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
color="black"
|
||||
_hover={{ bg: 'myGray.200' }}
|
||||
onClick={() =>
|
||||
setSelectedDatasets((prev) =>
|
||||
prev.filter((dataset) => dataset.datasetId !== item.datasetId)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
))}
|
||||
</VStack>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
{/* Modal footer button area */}
|
||||
<ModalFooter>
|
||||
<HStack spacing={4} w="full" align="center">
|
||||
<Spacer />
|
||||
<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>
|
||||
{!isRootEmpty && (
|
||||
<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={() => {
|
||||
// Close modal and return selected datasets
|
||||
onClose();
|
||||
onChange(selectedDatasets);
|
||||
}}
|
||||
>
|
||||
{t('common:Confirm')}
|
||||
{t('common:Cancel')}
|
||||
</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>
|
||||
</ModalFooter>
|
||||
</Flex>
|
||||
|
||||
{isQuickCreateOpen && (
|
||||
<QuickCreateDatasetModal
|
||||
parentId={parentId}
|
||||
onClose={onCloseQuickCreate}
|
||||
onSuccess={(newDataset) => {
|
||||
setSelectedDatasets((prev) => [...prev, newDataset]);
|
||||
loadDatasets();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -77,7 +77,8 @@ export function useDatasetSelect() {
|
|||
datasets: [],
|
||||
paths: []
|
||||
},
|
||||
loading: isFetching
|
||||
loading: isFetching,
|
||||
runAsync: loadDatasets
|
||||
} = useRequest2(
|
||||
async () => {
|
||||
const result = await Promise.all([
|
||||
|
|
@ -105,7 +106,8 @@ export function useDatasetSelect() {
|
|||
setSearchKey,
|
||||
datasets: data.datasets,
|
||||
paths: data.paths,
|
||||
isFetching
|
||||
isFetching,
|
||||
loadDatasets
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
|||
} = useDisclosure();
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
if (Array.isArray(item.value)) return item.value as SelectedDatasetType;
|
||||
return [] as SelectedDatasetType;
|
||||
if (Array.isArray(item.value)) return item.value as SelectedDatasetType[];
|
||||
return [] as SelectedDatasetType[];
|
||||
}, [item.value]);
|
||||
|
||||
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 { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useRouter } from 'next/router';
|
||||
import { TabEnum } from '../../../../../pages/dataset/detail/index';
|
||||
import {
|
||||
postCreateDatasetApiDatasetCollection,
|
||||
postCreateDatasetExternalFileCollection,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const requestLLMPargraph = async ({
|
|||
rawText: string;
|
||||
model: string;
|
||||
billId: string;
|
||||
paragraphChunkAIMode: ParagraphChunkAIModeEnum;
|
||||
paragraphChunkAIMode?: ParagraphChunkAIModeEnum;
|
||||
}) => {
|
||||
if (
|
||||
!global.feConfigs?.isPlus ||
|
||||
|
|
|
|||
Loading…
Reference in New Issue