V4.9.1 feature (#4206)
* fix: remove DefaultTeam (#4037) * fix :Get application bound knowledge base information logical rewrite (#4057) * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * fix :Get application bound knowledge base information logical rewrite * update package * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * fix: 文本提取不支持arrayString,arrayNumber等jsonSchema (#4079) * update doc ;perf: model test (#4098) * perf: extract array * update doc * perf: model test * perf: model test * perf: think tag parse (#4102) * chat quote reader (#3912) * init chat quote full text reader * linked structure * dataset data linked * optimize code * fix ts build * test finish * delete log * fix * fix ts * fix ts * remove nextId * initial scroll * fix * fix * perf: chunk read (#4109) * package * perf: chunk read * feat: api dataset support pdf parse;fix: chunk reader auth (#4117) * feat: api dataset support pdf parse * fix: chunk reader auth * feat: invitation link (#3979) * feat: invitation link schema and apis * feat: add invitation link * feat: member status: active, leave, forbidden * fix: expires show hours and minutes * feat: invalid invitation link hint * fix: typo * chore: fix typo & i18n * fix * pref: fe * feat: add ttl index for 30-day-clean-up * perf: invite member code (#4118) * perf: invite member code * fix: ts * fix: model test channel id;fix: quote reader (#4123) * fix: model test channel id * fix: quote reader * fix chat quote reader (#4125) * perf: model test;perf: sidebar trigger (#4127) * fix: import dataset step error;perf: ai proxy avatar (#4074) * perf: pg config params * perf: ai proxy avatar * fix: import dataset step error * feat: data input ux * perf: app dataset rewite * perf: model test * perf: sidebar trigger * lock * update nanoid version * fix: select component ux * fix: ts * fix: vitest * remove test * fix: prompt toolcall ui (#4139) * load log error adapt * fix: prompt toolcall ui * perf: commercial function tip * update package * pref: copy link (#4147) * fix(i18n): namespace (#4143) * hiden dataset source (#4152) * hiden dataset source * perf: reader * chore: move all tests into a single folder (#4160) * fix modal close scroll (#4162) * fix modal close scroll * update refresh * feat: rerank modal select and weight (#4164) * fix loadInitData refresh (#4169) * fix * fix * form input number default & api dataset max token * feat: mix search weight (#4170) * feat: mix search weight * feat: svg render * fix: avatar error remove (#4173) * fix: avatar error remove * fix: index * fix: guide * fix: auth * update package;fix: input data model ui (#4181) * update package * fix: ts * update config * update jieba package * add type sign * fix: input data ui * fix: page title refresh (#4186) * fix: ts * update jieba package * fix: page title refresh * fix: remove member length check when opening invite create modal (#4193) * add env to check internal ip (#4187) * fix: ts * update jieba package * add env to check internal ip * package * fix: jieba * reset package * update config * fix: jieba package * init shell * init version * change team reload * update jieba package (#4200) * update jieba package * package * update package * remove invalid code * action * package (#4201) * package * update package * remove invalid code * package * remove i18n tip (#4202) * doc (#4205) * fix: i18n (#4208) * fix: next config (#4207) * reset package * i18n * update config * i18n * remove log --------- Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: gggaaallleee <91131304+gggaaallleee@users.noreply.github.com> Co-authored-by: shilin <39396378+shilin66@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
@@ -18,7 +18,6 @@ import WorkorderButton from './WorkorderButton';
|
||||
|
||||
const Navbar = dynamic(() => import('./navbar'));
|
||||
const NavbarPhone = dynamic(() => import('./navbarPhone'));
|
||||
const UpdateInviteModal = dynamic(() => import('@/components/support/user/team/UpdateInviteModal'));
|
||||
const NotSufficientModal = dynamic(() => import('@/components/support/wallet/NotSufficientModal'));
|
||||
const SystemMsgModal = dynamic(() => import('@/components/support/user/inform/SystemMsgModal'));
|
||||
const ImportantInform = dynamic(() => import('@/components/support/user/inform/ImportantInform'));
|
||||
@@ -151,7 +150,6 @@ const Layout = ({ children }: { children: JSX.Element }) => {
|
||||
</Box>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
{!!userInfo && <UpdateInviteModal />}
|
||||
{notSufficientModalType && <NotSufficientModal type={notSufficientModalType} />}
|
||||
{!!userInfo && <SystemMsgModal />}
|
||||
{showUpdateNotification && (
|
||||
|
||||
@@ -85,7 +85,7 @@ export default React.memo(Markdown);
|
||||
function Code(e: any) {
|
||||
const { className, codeBlock, children } = e;
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const codeType = match?.[1];
|
||||
const codeType = match?.[1]?.toLowerCase();
|
||||
|
||||
const strChildren = String(children);
|
||||
|
||||
@@ -96,7 +96,7 @@ function Code(e: any) {
|
||||
if (codeType === CodeClassNameEnum.guide) {
|
||||
return <ChatGuide text={strChildren} />;
|
||||
}
|
||||
if (codeType === CodeClassNameEnum.questionGuide) {
|
||||
if (codeType === CodeClassNameEnum.questionguide) {
|
||||
return <QuestionGuide text={strChildren} />;
|
||||
}
|
||||
if (codeType === CodeClassNameEnum.echarts) {
|
||||
@@ -105,7 +105,7 @@ function Code(e: any) {
|
||||
if (codeType === CodeClassNameEnum.iframe) {
|
||||
return <IframeCodeBlock code={strChildren} />;
|
||||
}
|
||||
if (codeType && codeType.toLowerCase() === CodeClassNameEnum.html) {
|
||||
if (codeType === CodeClassNameEnum.html || codeType === CodeClassNameEnum.svg) {
|
||||
return (
|
||||
<IframeHtmlCodeBlock className={className} codeBlock={codeBlock} match={match}>
|
||||
{children}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export enum CodeClassNameEnum {
|
||||
guide = 'guide',
|
||||
questionGuide = 'questionGuide',
|
||||
questionguide = 'questionguide',
|
||||
mermaid = 'mermaid',
|
||||
echarts = 'echarts',
|
||||
quote = 'quote',
|
||||
@@ -8,6 +8,7 @@ export enum CodeClassNameEnum {
|
||||
latex = 'latex',
|
||||
iframe = 'iframe',
|
||||
html = 'html',
|
||||
svg = 'svg',
|
||||
video = 'video',
|
||||
audio = 'audio'
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ type Props = SelectProps & {
|
||||
disableTip?: string;
|
||||
};
|
||||
|
||||
const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
const OneRowSelector = ({ list, onChange, disableTip, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } =
|
||||
useSystemStore();
|
||||
@@ -96,12 +96,12 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
placeholder={t('common:not_model_config')}
|
||||
h={'40px'}
|
||||
{...props}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
if (e === 'price') {
|
||||
onOpen();
|
||||
return;
|
||||
}
|
||||
return onchange?.(e);
|
||||
return onChange?.(e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -110,7 +110,7 @@ const OneRowSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const MultipleRowSelector = ({ list, onchange, disableTip, placeholder, ...props }: Props) => {
|
||||
const MultipleRowSelector = ({ list, onChange, disableTip, placeholder, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } =
|
||||
useSystemStore();
|
||||
@@ -178,9 +178,9 @@ const MultipleRowSelector = ({ list, onchange, disableTip, placeholder, ...props
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: string[]) => {
|
||||
return onchange?.(e[1]);
|
||||
return onChange?.(e[1]);
|
||||
},
|
||||
[onchange]
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const SelectedModel = useMemo(() => {
|
||||
|
||||
@@ -26,7 +26,7 @@ const I18nLngSelector = () => {
|
||||
<MySelect
|
||||
value={i18n.language}
|
||||
list={list}
|
||||
onchange={(val: any) => {
|
||||
onChange={(val: any) => {
|
||||
const lang = val;
|
||||
onChangeLng(lang);
|
||||
}}
|
||||
|
||||
@@ -1,22 +1,40 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
interface Props extends BoxProps {}
|
||||
interface Props extends BoxProps {
|
||||
externalTrigger?: Boolean;
|
||||
}
|
||||
|
||||
const SideBar = (e?: Props) => {
|
||||
const {
|
||||
w = ['100%', '0 0 250px', '0 0 270px', '0 0 290px', '0 0 310px'],
|
||||
w = ['100%', '0 0 250px', '0 0 250px', '0 0 270px', '0 0 290px'],
|
||||
children,
|
||||
externalTrigger,
|
||||
...props
|
||||
} = e || {};
|
||||
|
||||
const [foldSideBar, setFoldSideBar] = useState(false);
|
||||
const [isFolded, setIsFolded] = useState(false);
|
||||
|
||||
// 保存上一次折叠状态
|
||||
const preFoledStatus = useRef<Boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (externalTrigger) {
|
||||
setIsFolded(true);
|
||||
preFoledStatus.current = isFolded;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
setIsFolded(preFoledStatus.current);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [externalTrigger]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
position={'relative'}
|
||||
flex={foldSideBar ? '0 0 0' : w}
|
||||
flex={isFolded ? '0 0 0' : w}
|
||||
w={['100%', 0]}
|
||||
h={'100%'}
|
||||
zIndex={1}
|
||||
@@ -40,7 +58,7 @@ const SideBar = (e?: Props) => {
|
||||
bg={'rgba(0,0,0,0.5)'}
|
||||
cursor={'pointer'}
|
||||
transition={'0.2s'}
|
||||
{...(foldSideBar
|
||||
{...(isFolded
|
||||
? {
|
||||
opacity: 0.6
|
||||
}
|
||||
@@ -48,16 +66,16 @@ const SideBar = (e?: Props) => {
|
||||
visibility: 'hidden',
|
||||
opacity: 0
|
||||
})}
|
||||
onClick={() => setFoldSideBar(!foldSideBar)}
|
||||
onClick={() => setIsFolded(!isFolded)}
|
||||
>
|
||||
<MyIcon
|
||||
name={'common/backLight'}
|
||||
transform={foldSideBar ? 'rotate(180deg)' : ''}
|
||||
transform={isFolded ? 'rotate(180deg)' : ''}
|
||||
w={'14px'}
|
||||
color={'white'}
|
||||
/>
|
||||
</Flex>
|
||||
<Box position={'relative'} h={'100%'} overflow={foldSideBar ? 'hidden' : 'visible'}>
|
||||
<Box position={'relative'} h={'100%'} overflow={isFolded ? 'hidden' : 'visible'}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LOGO_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import Head from 'next/head';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
const NextHead = ({ title, icon, desc }: { title?: string; icon?: string; desc?: string }) => {
|
||||
const formatIcon = useMemo(() => {
|
||||
@@ -11,13 +11,6 @@ const NextHead = ({ title, icon, desc }: { title?: string; icon?: string; desc?:
|
||||
return LOGO_ICON;
|
||||
}, [icon]);
|
||||
|
||||
useEffect(() => {
|
||||
// Force update document title
|
||||
if (title) {
|
||||
document.title = title;
|
||||
}
|
||||
}, [title]);
|
||||
|
||||
return (
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
|
||||
@@ -154,7 +154,7 @@ const AIChatSettingsModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={onChangeModel}
|
||||
onChange={onChangeModel}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
@@ -385,7 +385,7 @@ const AIChatSettingsModal = ({
|
||||
label: item
|
||||
}))}
|
||||
value={responseFormat}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue(NodeInputKeyEnum.aiChatResponseFormat, e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -212,7 +212,7 @@ const ModelTable = () => {
|
||||
w={'200px'}
|
||||
bg={'myGray.50'}
|
||||
value={provider}
|
||||
onchange={setProvider}
|
||||
onChange={setProvider}
|
||||
list={filterProviderList}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -224,7 +224,7 @@ const ModelTable = () => {
|
||||
w={'150px'}
|
||||
bg={'myGray.50'}
|
||||
value={modelType}
|
||||
onchange={setModelType}
|
||||
onChange={setModelType}
|
||||
list={selectModelTypeList.current}
|
||||
/>
|
||||
</HStack>
|
||||
|
||||
@@ -77,7 +77,7 @@ const SettingLLMModel = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...defaultData,
|
||||
model: e
|
||||
|
||||
@@ -2,13 +2,15 @@ import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
Divider,
|
||||
Flex,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Switch,
|
||||
useTheme
|
||||
Slider,
|
||||
SliderTrack,
|
||||
SliderFilledTrack,
|
||||
SliderThumb
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
@@ -17,30 +19,18 @@ import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import SelectAiModel from '@/components/Select/AIModelSelector';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyTextarea from '@/components/common/Textarea/MyTextarea';
|
||||
import { defaultDatasetMaxTokens } from '@fastgpt/global/core/app/constants';
|
||||
import InputSlider from '@fastgpt/web/components/common/MySlider/InputSlider';
|
||||
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
||||
import { AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
export type DatasetParamsProps = {
|
||||
searchMode: `${DatasetSearchModeEnum}`;
|
||||
limit?: number;
|
||||
similarity?: number;
|
||||
usingReRank?: boolean;
|
||||
datasetSearchUsingExtensionQuery?: boolean;
|
||||
datasetSearchExtensionModel?: string;
|
||||
datasetSearchExtensionBg?: string;
|
||||
|
||||
maxTokens?: number; // limit max tokens
|
||||
};
|
||||
enum SearchSettingTabEnum {
|
||||
searchMode = 'searchMode',
|
||||
limit = 'limit',
|
||||
@@ -51,17 +41,22 @@ const DatasetParamsModal = ({
|
||||
searchMode = DatasetSearchModeEnum.embedding,
|
||||
limit,
|
||||
similarity,
|
||||
embeddingWeight,
|
||||
usingReRank,
|
||||
maxTokens = defaultDatasetMaxTokens,
|
||||
rerankModel,
|
||||
rerankWeight,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel,
|
||||
datasetSearchExtensionBg,
|
||||
maxTokens = defaultDatasetMaxTokens,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: DatasetParamsProps & { onClose: () => void; onSuccess: (e: DatasetParamsProps) => void }) => {
|
||||
}: AppDatasetSearchParamsType & {
|
||||
maxTokens?: number; // limit max tokens
|
||||
onClose: () => void;
|
||||
onSuccess: (e: AppDatasetSearchParamsType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { teamPlanStatus } = useUserStore();
|
||||
const { reRankModelList, llmModelList, defaultModels } = useSystemStore();
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
@@ -72,28 +67,41 @@ const DatasetParamsModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
const reRankModelSelectList = (() =>
|
||||
reRankModelList.map((item) => ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
})))();
|
||||
|
||||
const { register, setValue, getValues, handleSubmit, watch } =
|
||||
useForm<AppDatasetSearchParamsType>({
|
||||
defaultValues: {
|
||||
searchMode,
|
||||
embeddingWeight: embeddingWeight || 0.5,
|
||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||
rerankModel: rerankModel || defaultModels?.rerank?.model,
|
||||
rerankWeight: rerankWeight || 0.5,
|
||||
limit,
|
||||
similarity,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel || defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
});
|
||||
|
||||
const searchModeWatch = watch('searchMode');
|
||||
const embeddingWeightWatch = watch('embeddingWeight');
|
||||
const fullTextWeightWatch = useMemo(() => {
|
||||
const val = 1 - (embeddingWeightWatch || 0.5);
|
||||
return Number(val.toFixed(2));
|
||||
}, [embeddingWeightWatch]);
|
||||
|
||||
const { register, setValue, getValues, handleSubmit, watch } = useForm<DatasetParamsProps>({
|
||||
defaultValues: {
|
||||
limit,
|
||||
similarity,
|
||||
searchMode,
|
||||
usingReRank: !!usingReRank && teamPlanStatus?.standardConstants?.permissionReRank !== false,
|
||||
datasetSearchUsingExtensionQuery,
|
||||
datasetSearchExtensionModel: datasetSearchExtensionModel || defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg
|
||||
}
|
||||
});
|
||||
const datasetSearchUsingCfrForm = watch('datasetSearchUsingExtensionQuery');
|
||||
const queryExtensionModel = watch('datasetSearchExtensionModel');
|
||||
const cfbBgDesc = watch('datasetSearchExtensionBg');
|
||||
const usingReRankWatch = watch('usingReRank');
|
||||
const searchModeWatch = watch('searchMode');
|
||||
|
||||
const searchModeList = useMemo(() => {
|
||||
const list = Object.values(DatasetSearchModeMap);
|
||||
return list;
|
||||
}, []);
|
||||
const usingReRankWatch = watch('usingReRank');
|
||||
const reRankModelWatch = watch('rerankModel');
|
||||
const rerankWeightWatch = watch('rerankWeight');
|
||||
|
||||
const showSimilarity = useMemo(() => {
|
||||
if (similarity === undefined) return false;
|
||||
@@ -134,93 +142,160 @@ const DatasetParamsModal = ({
|
||||
title={t('common:core.dataset.search.Dataset Search Params')}
|
||||
w={['90vw', '550px']}
|
||||
>
|
||||
<ModalBody flex={'auto'} overflow={'auto'}>
|
||||
<ModalBody flex={'auto'} overflow={'auto'} px={[4, 10]}>
|
||||
<LightRowTabs<SearchSettingTabEnum>
|
||||
width={'100%'}
|
||||
mb={3}
|
||||
list={[
|
||||
{
|
||||
icon: 'modal/setting',
|
||||
icon: 'common/setting',
|
||||
label: t('common:core.dataset.search.search mode'),
|
||||
value: SearchSettingTabEnum.searchMode
|
||||
},
|
||||
{
|
||||
icon: 'support/outlink/apikeyFill',
|
||||
icon: 'core/dataset/searchfilter',
|
||||
label: t('common:core.dataset.search.Filter'),
|
||||
value: SearchSettingTabEnum.limit
|
||||
},
|
||||
{
|
||||
label: t('common:core.module.template.Query extension'),
|
||||
value: SearchSettingTabEnum.queryExtension,
|
||||
icon: '/imgs/workflow/cfr.svg'
|
||||
icon: 'core/dataset/questionExtension'
|
||||
}
|
||||
]}
|
||||
inlineStyles={{
|
||||
borderBottomColor: 'myGray.200',
|
||||
borderBottom: '1px solid'
|
||||
}}
|
||||
value={currentTabType}
|
||||
onChange={setCurrentTabType}
|
||||
/>
|
||||
{currentTabType === SearchSettingTabEnum.searchMode && (
|
||||
<>
|
||||
<MyRadio
|
||||
gridGap={2}
|
||||
gridTemplateColumns={'repeat(1,1fr)'}
|
||||
list={searchModeList}
|
||||
value={getValues('searchMode')}
|
||||
<Box mt={3}>
|
||||
<LeftRadio<`${DatasetSearchModeEnum}`>
|
||||
py={2.5}
|
||||
gridGap={4}
|
||||
list={[
|
||||
{
|
||||
title: t('common:core.dataset.search.mode.embedding'),
|
||||
desc: t('common:core.dataset.search.mode.embedding desc'),
|
||||
value: DatasetSearchModeEnum.embedding
|
||||
},
|
||||
{
|
||||
title: t('common:core.dataset.search.mode.fullTextRecall'),
|
||||
desc: t('common:core.dataset.search.mode.fullTextRecall desc'),
|
||||
value: DatasetSearchModeEnum.fullTextRecall
|
||||
},
|
||||
{
|
||||
title: t('common:core.dataset.search.mode.mixedRecall'),
|
||||
desc: t('common:core.dataset.search.mode.mixedRecall desc'),
|
||||
value: DatasetSearchModeEnum.mixedRecall,
|
||||
children: searchModeWatch === DatasetSearchModeEnum.mixedRecall && (
|
||||
<Box mt={3}>
|
||||
<HStack justifyContent={'space-between'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'sm'} color={'myGray.900'}>
|
||||
{t('common:core.dataset.search.mode.embedding')}
|
||||
</Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{embeddingWeightWatch}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box fontSize={'sm'} color={'myGray.900'}>
|
||||
{t('common:core.dataset.search.score.fullText')}
|
||||
</Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{fullTextWeightWatch}
|
||||
</Box>
|
||||
</Flex>
|
||||
</HStack>
|
||||
<Slider
|
||||
defaultValue={embeddingWeightWatch}
|
||||
min={0.1}
|
||||
max={0.9}
|
||||
step={0.01}
|
||||
onChange={(e) => {
|
||||
setValue('embeddingWeight', Number(e.toFixed(2)));
|
||||
}}
|
||||
>
|
||||
<SliderTrack bg={'#F9518E'}>
|
||||
<SliderFilledTrack bg={'#3370FF'} />
|
||||
</SliderTrack>
|
||||
<SliderThumb boxShadow={'none'} bg={'none'}>
|
||||
<MyIcon transform={'translateY(10px)'} name={'sliderTag'} w={'1rem'} />
|
||||
</SliderThumb>
|
||||
</Slider>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
]}
|
||||
value={searchModeWatch}
|
||||
onChange={(e) => {
|
||||
setValue('searchMode', e as `${DatasetSearchModeEnum}`);
|
||||
setRefresh(!refresh);
|
||||
setValue('searchMode', e);
|
||||
}}
|
||||
/>
|
||||
{/* Rerank */}
|
||||
<>
|
||||
<Divider my={4} />
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
cursor={'pointer'}
|
||||
userSelect={'none'}
|
||||
py={3}
|
||||
px={4}
|
||||
border={theme.borders.sm}
|
||||
borderWidth={'1.5px'}
|
||||
borderRadius={'md'}
|
||||
position={'relative'}
|
||||
{...(getValues('usingReRank')
|
||||
? {
|
||||
borderColor: 'primary.400'
|
||||
}
|
||||
: {})}
|
||||
onClick={(e) => {
|
||||
if (!showReRank) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:core.ai.Not deploy rerank model')
|
||||
});
|
||||
}
|
||||
if (
|
||||
teamPlanStatus?.standardConstants &&
|
||||
!teamPlanStatus?.standardConstants?.permissionReRank
|
||||
) {
|
||||
return toast({
|
||||
status: 'warning',
|
||||
title: t('common:support.team.limit.No permission rerank')
|
||||
});
|
||||
}
|
||||
setValue('usingReRank', !getValues('usingReRank'));
|
||||
setRefresh((state) => !state);
|
||||
}}
|
||||
>
|
||||
<MyIcon name="core/dataset/rerank" w={'18px'} mr={'14px'} />
|
||||
<Box pr={2} color={'myGray.800'} flex={'1 0 0'}>
|
||||
<Box fontSize={'sm'}>{t('common:core.dataset.search.ReRank')}</Box>
|
||||
<Box fontSize={'xs'} color={'myGray.500'}>
|
||||
{t('common:core.dataset.search.ReRank desc')}
|
||||
<HStack mt={6} justifyContent={'space-between'}>
|
||||
<FormLabel>
|
||||
{t('common:core.dataset.search.ReRank')}
|
||||
<QuestionTip ml={0.5} label={t('common:core.dataset.search.ReRank desc')} />
|
||||
</FormLabel>
|
||||
{!showReRank ? (
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{t('common:core.ai.Not deploy rerank model')}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box position={'relative'} w={'18px'} h={'18px'}>
|
||||
<Checkbox colorScheme="primary" isChecked={getValues('usingReRank')} size="lg" />
|
||||
<Box position={'absolute'} top={0} right={0} bottom={0} left={0} zIndex={1}></Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
) : teamPlanStatus?.standardConstants &&
|
||||
!teamPlanStatus?.standardConstants?.permissionReRank ? (
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{t('common:support.team.limit.No permission rerank')}
|
||||
</Box>
|
||||
) : (
|
||||
<Switch {...register('usingReRank')} />
|
||||
)}
|
||||
</HStack>
|
||||
{usingReRankWatch && (
|
||||
<>
|
||||
<HStack mt={3} justifyContent={'space-between'}>
|
||||
<Box fontSize={'sm'} flex={'0 0 100px'} color={'myGray.700'}>
|
||||
{t('common:rerank_weight')}
|
||||
</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<InputSlider
|
||||
min={0.1}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={rerankWeightWatch}
|
||||
onChange={(val) => {
|
||||
setValue(
|
||||
NodeInputKeyEnum.datasetSearchRerankWeight,
|
||||
Number(val.toFixed(2))
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
<HStack mt={3}>
|
||||
<Box fontSize={'sm'} flex={'0 0 100px'} color={'myGray.700'}>
|
||||
{t('common:model.type.reRank')}
|
||||
</Box>
|
||||
<Box flex={'1 0 0'}>
|
||||
<SelectAiModel
|
||||
bg={'myGray.50'}
|
||||
h={'36px'}
|
||||
value={reRankModelWatch}
|
||||
list={reRankModelSelectList}
|
||||
onChange={(val) => {
|
||||
setValue(NodeInputKeyEnum.datasetSearchRerankModel, val);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
</Box>
|
||||
)}
|
||||
{currentTabType === SearchSettingTabEnum.limit && (
|
||||
<Box pt={5}>
|
||||
@@ -262,7 +337,7 @@ const DatasetParamsModal = ({
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box color={'myGray.500'}>
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{t('common:core.dataset.search.No support similarity')}
|
||||
</Box>
|
||||
)}
|
||||
@@ -290,7 +365,7 @@ const DatasetParamsModal = ({
|
||||
width={'100%'}
|
||||
value={queryExtensionModel}
|
||||
list={chatModelSelectList}
|
||||
onchange={(val: any) => {
|
||||
onChange={(val: any) => {
|
||||
setValue('datasetSearchExtensionModel', val);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,6 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import DatasetSelectContainer, { useDatasetSelect } from '@/components/core/dataset/SelectModal';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
@@ -35,29 +34,24 @@ export const DatasetSelectModal = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const [selectedDatasets, setSelectedDatasets] = useState<SelectedDatasetType>(
|
||||
defaultSelectedDatasets.filter((dataset) => {
|
||||
return allDatasets.find((item) => item._id === dataset.datasetId);
|
||||
})
|
||||
);
|
||||
const [selectedDatasets, setSelectedDatasets] =
|
||||
useState<SelectedDatasetType>(defaultSelectedDatasets);
|
||||
const { toast } = useToast();
|
||||
const { paths, setParentId, datasets, isFetching } = useDatasetSelect();
|
||||
const { Loading } = useLoading();
|
||||
|
||||
const filterDatasets = useMemo(() => {
|
||||
return {
|
||||
selected: allDatasets.filter((item) =>
|
||||
const filtered = {
|
||||
selected: datasets.filter((item) =>
|
||||
selectedDatasets.find((dataset) => dataset.datasetId === item._id)
|
||||
),
|
||||
unSelected: datasets.filter(
|
||||
(item) => !selectedDatasets.find((dataset) => dataset.datasetId === item._id)
|
||||
)
|
||||
};
|
||||
}, [datasets, allDatasets, selectedDatasets]);
|
||||
const activeVectorModel = allDatasets.find(
|
||||
(dataset) => dataset._id === selectedDatasets[0]?.datasetId
|
||||
)?.vectorModel?.model;
|
||||
return filtered;
|
||||
}, [datasets, selectedDatasets]);
|
||||
const activeVectorModel = defaultSelectedDatasets[0]?.vectorModel?.model;
|
||||
|
||||
return (
|
||||
<DatasetSelectContainer
|
||||
@@ -150,7 +144,15 @@ export const DatasetSelectModal = ({
|
||||
title: t('common:dataset.Select Dataset Tips')
|
||||
});
|
||||
}
|
||||
setSelectedDatasets((state) => [...state, { datasetId: item._id }]);
|
||||
setSelectedDatasets((state) => [
|
||||
...state,
|
||||
{
|
||||
datasetId: item._id,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
vectorModel: item.vectorModel
|
||||
}
|
||||
]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -200,7 +202,7 @@ export const DatasetSelectModal = ({
|
||||
onClick={() => {
|
||||
// filter out the dataset that is not in the kList
|
||||
const filterDatasets = selectedDatasets.filter((dataset) => {
|
||||
return allDatasets.find((item) => item._id === dataset.datasetId);
|
||||
return datasets.find((item) => item._id === dataset.datasetId);
|
||||
});
|
||||
|
||||
onClose();
|
||||
|
||||
@@ -54,7 +54,6 @@ const InputGuideConfig = ({
|
||||
onChange: (e: ChatInputGuideConfigType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { chatT } = useI18n();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const {
|
||||
isOpen: isOpenLexiconConfig,
|
||||
@@ -87,11 +86,11 @@ const InputGuideConfig = ({
|
||||
<Flex alignItems={'center'}>
|
||||
<MyIcon name={'core/app/inputGuides'} mr={2} w={'20px'} />
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel color={'myGray.600'}>{chatT('input_guide')}</FormLabel>
|
||||
<FormLabel color={'myGray.600'}>{t('chat:input_guide')}</FormLabel>
|
||||
<ChatFunctionTip type={'inputGuide'} />
|
||||
</Flex>
|
||||
<Box flex={1} />
|
||||
<MyTooltip label={chatT('config_input_guide')}>
|
||||
<MyTooltip label={t('chat:config_input_guide')}>
|
||||
<Button
|
||||
variant={'transparentBase'}
|
||||
iconSpacing={1}
|
||||
@@ -104,7 +103,7 @@ const InputGuideConfig = ({
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
<MyModal
|
||||
title={chatT('input_guide')}
|
||||
title={t('chat:input_guide')}
|
||||
iconSrc="core/app/inputGuides"
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
@@ -126,7 +125,7 @@ const InputGuideConfig = ({
|
||||
{isOpenQuestionGuide && (
|
||||
<>
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
<FormLabel>{chatT('input_guide_lexicon')}</FormLabel>
|
||||
<FormLabel>{t('chat:input_guide_lexicon')}</FormLabel>
|
||||
<Box fontSize={'xs'} px={2} bg={'myGray.100'} ml={1} rounded={'full'}>
|
||||
{total}
|
||||
</Box>
|
||||
@@ -144,7 +143,7 @@ const InputGuideConfig = ({
|
||||
</Flex>
|
||||
<>
|
||||
<Flex mt={8} alignItems={'center'}>
|
||||
<FormLabel>{chatT('custom_input_guide_url')}</FormLabel>
|
||||
<FormLabel>{t('chat:custom_input_guide_url')}</FormLabel>
|
||||
<Flex
|
||||
onClick={() => window.open(getDocPath('/docs/guide/course/chat_input_guide/'))}
|
||||
color={'primary.700'}
|
||||
@@ -181,7 +180,7 @@ const InputGuideConfig = ({
|
||||
export default React.memo(InputGuideConfig);
|
||||
|
||||
const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () => void }) => {
|
||||
const { chatT, commonT } = useI18n();
|
||||
const { commonT } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const { File, onOpen: onOpenSelectFile } = useSelectFile({
|
||||
@@ -232,7 +231,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
if (res.insertLength < textList.length) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: chatT('insert_input_guide,_some_data_already_exists', { len: res.insertLength })
|
||||
title: t('chat:insert_input_guide,_some_data_already_exists', { len: res.insertLength })
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
@@ -301,7 +300,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
title={chatT('config_input_guide_lexicon_title')}
|
||||
title={t('chat:config_input_guide_lexicon_title')}
|
||||
iconSrc="core/app/inputGuides"
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
@@ -338,7 +337,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
});
|
||||
}}
|
||||
>
|
||||
<QuestionTip ml={-2} label={chatT('csv_input_lexicon_tip')} />
|
||||
<QuestionTip ml={-2} label={t('chat:csv_input_lexicon_tip')} />
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box px={8}>
|
||||
@@ -394,7 +393,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
<MyInput
|
||||
autoFocus
|
||||
rightIcon={<MyIcon name={'save'} w={'14px'} cursor={'pointer'} />}
|
||||
placeholder={chatT('new_input_guide_lexicon')}
|
||||
placeholder={t('chat:new_input_guide_lexicon')}
|
||||
onBlur={(e) => {
|
||||
createNewData([e.target.value.trim()]);
|
||||
}}
|
||||
@@ -411,7 +410,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () =>
|
||||
px={8}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'sm'}
|
||||
EmptyChildren={<EmptyTip text={chatT('chat_input_guide_lexicon_is_empty')} />}
|
||||
EmptyChildren={<EmptyTip text={t('chat:chat_input_guide_lexicon_is_empty')} />}
|
||||
>
|
||||
{scrollDataList.map((data, index) => {
|
||||
const item = data.data;
|
||||
|
||||
@@ -125,7 +125,7 @@ const QGConfigModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
onChange({
|
||||
...value,
|
||||
model: e
|
||||
|
||||
@@ -22,7 +22,6 @@ export default function InputGuideBox({
|
||||
onSend: (text: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { chatT } = useI18n();
|
||||
const chatInputGuide = useContextSelector(ChatBoxContext, (v) => v.chatInputGuide);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
|
||||
@@ -65,9 +64,9 @@ export default function InputGuideBox({
|
||||
>
|
||||
<Flex alignItems={'center'} fontSize={'sm'} color={'myGray.600'} gap={2} mb={2} px={2}>
|
||||
<MyIcon name={'union'} />
|
||||
<Box>{chatT('input_guide')}</Box>
|
||||
<Box>{t('chat:input_guide')}</Box>
|
||||
</Flex>
|
||||
{data.map((item, index) => (
|
||||
{data.map((item) => (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
as={'li'}
|
||||
|
||||
@@ -59,7 +59,7 @@ type Props = BasicProps & {
|
||||
const RenderQuestionGuide = ({ questionGuides }: { questionGuides: string[] }) => {
|
||||
return (
|
||||
<Markdown
|
||||
source={`\`\`\`${CodeClassNameEnum.questionGuide}
|
||||
source={`\`\`\`${CodeClassNameEnum.questionguide}
|
||||
${JSON.stringify(questionGuides)}`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, useTheme } from '@chakra-ui/react';
|
||||
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import QuoteItem, { formatScore } from '@/components/core/dataset/QuoteItem';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import { getQuoteDataList } from '@/web/core/chat/api';
|
||||
|
||||
const QuoteList = React.memo(function QuoteList({
|
||||
chatItemDataId = '',
|
||||
rawSearch = []
|
||||
}: {
|
||||
chatItemDataId?: string;
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { chatId, appId, outLinkAuthData } = useChatStore();
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
chatItemDataId,
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
);
|
||||
|
||||
const { data: quoteList } = useRequest2(
|
||||
async () =>
|
||||
await getQuoteDataList({
|
||||
datasetDataIdList: rawSearch.map((item) => item.id),
|
||||
collectionIdList: [...new Set(rawSearch.map((item) => item.collectionId))],
|
||||
chatItemDataId,
|
||||
appId,
|
||||
chatId,
|
||||
...outLinkAuthData
|
||||
}),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const formatedDataList = useMemo(() => {
|
||||
return rawSearch
|
||||
.map((item) => {
|
||||
const currentFilterItem = quoteList?.find((res) => res._id === item.id);
|
||||
|
||||
return {
|
||||
...item,
|
||||
q: currentFilterItem?.q || '',
|
||||
a: currentFilterItem?.a || ''
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
const aScore = formatScore(a.score);
|
||||
const bScore = formatScore(b.score);
|
||||
return (bScore.primaryScore?.value || 0) - (aScore.primaryScore?.value || 0);
|
||||
});
|
||||
}, [quoteList, rawSearch]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{formatedDataList.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
flex={'1 0 0'}
|
||||
p={2}
|
||||
borderRadius={'sm'}
|
||||
border={theme.borders.base}
|
||||
_notLast={{ mb: 2 }}
|
||||
_hover={{ '& .hover-data': { display: 'flex' } }}
|
||||
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
|
||||
>
|
||||
<QuoteItem
|
||||
quoteItem={item}
|
||||
canViewSource={showRawSource}
|
||||
canEditDataset={showRouteToDatasetDetail}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default QuoteList;
|
||||
@@ -1,129 +0,0 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ModalBody, Box, useTheme } from '@chakra-ui/react';
|
||||
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import QuoteItem from '@/components/core/dataset/QuoteItem';
|
||||
import RawSourceBox from '@/components/core/dataset/RawSourceBox';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const QuoteModal = ({
|
||||
rawSearch = [],
|
||||
onClose,
|
||||
chatItemId,
|
||||
metadata
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
onClose: () => void;
|
||||
chatItemId: string;
|
||||
metadata?: {
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
sourceName: string;
|
||||
};
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const filterResults = useMemo(
|
||||
() =>
|
||||
metadata
|
||||
? rawSearch.filter(
|
||||
(item) =>
|
||||
item.collectionId === metadata.collectionId && item.sourceId === metadata.sourceId
|
||||
)
|
||||
: rawSearch,
|
||||
[metadata, rawSearch]
|
||||
);
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
chatItemId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
onClose={onClose}
|
||||
h={['90vh', '80vh']}
|
||||
isCentered
|
||||
minW={['90vw', '600px']}
|
||||
iconSrc={!!metadata ? undefined : getWebReqUrl('/imgs/modal/quote.svg')}
|
||||
title={
|
||||
<Box>
|
||||
{metadata ? (
|
||||
<RawSourceBox {...metadata} {...RawSourceBoxProps} canView={showRawSource} />
|
||||
) : (
|
||||
<>{t('common:core.chat.Quote Amount', { amount: rawSearch.length })}</>
|
||||
)}
|
||||
<Box fontSize={'xs'} color={'myGray.500'} fontWeight={'normal'}>
|
||||
{t('common:core.chat.quote.Quote Tip')}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<ModalBody>
|
||||
<QuoteList rawSearch={filterResults} chatItemId={chatItemId} />
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuoteModal;
|
||||
|
||||
export const QuoteList = React.memo(function QuoteList({
|
||||
chatItemId,
|
||||
rawSearch = []
|
||||
}: {
|
||||
chatItemId?: string;
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
const RawSourceBoxProps = useContextSelector(ChatBoxContext, (v) => ({
|
||||
chatItemId,
|
||||
appId: v.appId,
|
||||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{rawSearch.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
flex={'1 0 0'}
|
||||
p={2}
|
||||
borderRadius={'sm'}
|
||||
border={theme.borders.base}
|
||||
_notLast={{ mb: 2 }}
|
||||
_hover={{ '& .hover-data': { display: 'flex' } }}
|
||||
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
|
||||
>
|
||||
<QuoteItem
|
||||
quoteItem={item}
|
||||
canViewSource={showRawSource}
|
||||
canEditDataset={showRouteToDatasetDetail}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -14,8 +14,8 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
|
||||
import { useSize } from 'ahooks';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'));
|
||||
const ContextModal = dynamic(() => import('./ContextModal'));
|
||||
const WholeResponseModal = dynamic(() => import('../../../components/WholeResponseModal'));
|
||||
|
||||
@@ -30,6 +30,7 @@ const ResponseTags = ({
|
||||
const { t } = useTranslation();
|
||||
const quoteListRef = React.useRef<HTMLDivElement>(null);
|
||||
const dataId = historyItem.dataId;
|
||||
const chatTime = historyItem.time || new Date();
|
||||
|
||||
const {
|
||||
totalQuoteList: quoteList = [],
|
||||
@@ -38,17 +39,15 @@ const ResponseTags = ({
|
||||
historyPreviewLength = 0
|
||||
} = useMemo(() => addStatisticalDataToHistoryItem(historyItem), [historyItem]);
|
||||
|
||||
const [quoteModalData, setQuoteModalData] = useState<{
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
metadata?: {
|
||||
collectionId: string;
|
||||
sourceId?: string;
|
||||
sourceName: string;
|
||||
};
|
||||
}>();
|
||||
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
|
||||
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const appId = useContextSelector(ChatBoxContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(ChatBoxContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(ChatBoxContext, (v) => v.outLinkAuthData);
|
||||
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const notSharePage = useMemo(() => chatType !== 'share', [chatType]);
|
||||
|
||||
const {
|
||||
@@ -67,6 +66,7 @@ const ResponseTags = ({
|
||||
? quoteListRef.current.scrollHeight > (isPc ? 50 : 55)
|
||||
: true;
|
||||
|
||||
const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const sourceList = useMemo(() => {
|
||||
return Object.values(
|
||||
quoteList.reduce((acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
@@ -81,7 +81,8 @@ const ResponseTags = ({
|
||||
sourceName: item.sourceName,
|
||||
sourceId: item.sourceId,
|
||||
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName }),
|
||||
collectionId: item.collectionId
|
||||
collectionId: item.collectionId,
|
||||
datasetId: item.datasetId
|
||||
}));
|
||||
}, [quoteList]);
|
||||
|
||||
@@ -99,7 +100,11 @@ const ResponseTags = ({
|
||||
<>
|
||||
<Flex justifyContent={'space-between'} alignItems={'center'}>
|
||||
<Box width={'100%'}>
|
||||
<ChatBoxDivider icon="core/chat/quoteFill" text={t('common:core.chat.Quote')} />
|
||||
<ChatBoxDivider
|
||||
icon="core/chat/quoteFill"
|
||||
text={t('common:core.chat.Quote')}
|
||||
iconColor="#E82F72"
|
||||
/>
|
||||
</Box>
|
||||
{quoteFolded && quoteIsOverflow && (
|
||||
<MyIcon
|
||||
@@ -135,15 +140,13 @@ const ResponseTags = ({
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{sourceList.map((item) => {
|
||||
{sourceList.map((item, index) => {
|
||||
return (
|
||||
<MyTooltip key={item.collectionId} label={t('common:core.chat.quote.Read Quote')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={'sm'}
|
||||
py={1.5}
|
||||
px={2}
|
||||
borderRadius={'sm'}
|
||||
_hover={{
|
||||
'.controller': {
|
||||
@@ -155,20 +158,60 @@ const ResponseTags = ({
|
||||
cursor={'pointer'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setQuoteModalData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
collectionId: item.collectionId,
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName
|
||||
}
|
||||
});
|
||||
|
||||
if (isShowReadRawSource) {
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionId: item.collectionId,
|
||||
sourceId: item.sourceId || '',
|
||||
sourceName: item.sourceName,
|
||||
datasetId: item.datasetId,
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionIdList: [item.collectionId],
|
||||
sourceId: item.sourceId || '',
|
||||
sourceName: item.sourceName,
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
height={6}
|
||||
>
|
||||
<MyIcon name={item.icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box className="textEllipsis3" wordBreak={'break-all'} flex={'1 0 0'}>
|
||||
{item.sourceName}
|
||||
</Box>
|
||||
<Flex
|
||||
color={'myGray.500'}
|
||||
bg={'myGray.150'}
|
||||
w={4}
|
||||
justifyContent={'center'}
|
||||
fontSize={'10px'}
|
||||
h={'full'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
{index + 1}
|
||||
</Flex>
|
||||
<Flex px={1.5}>
|
||||
<MyIcon name={item.icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box
|
||||
className="textEllipsis3"
|
||||
wordBreak={'break-all'}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'mini'}
|
||||
>
|
||||
{item.sourceName}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
);
|
||||
@@ -196,7 +239,20 @@ const ResponseTags = ({
|
||||
colorSchema="blue"
|
||||
type="borderSolid"
|
||||
cursor={'pointer'}
|
||||
onClick={() => setQuoteModalData({ rawSearch: quoteList })}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setQuoteData({
|
||||
rawSearch: quoteList,
|
||||
metadata: {
|
||||
appId,
|
||||
chatId,
|
||||
chatItemDataId: dataId,
|
||||
collectionIdList: [...new Set(quoteList.map((item) => item.collectionId))],
|
||||
outLinkAuthData
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t('chat:citations', { num: quoteList.length })}
|
||||
</MyTag>
|
||||
@@ -246,15 +302,10 @@ const ResponseTags = ({
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal
|
||||
{...quoteModalData}
|
||||
chatItemId={historyItem.dataId}
|
||||
onClose={() => setQuoteModalData(undefined)}
|
||||
/>
|
||||
)}
|
||||
{isOpenContextModal && <ContextModal dataId={dataId} onClose={onCloseContextModal} />}
|
||||
{isOpenWholeModal && <WholeResponseModal dataId={dataId} onClose={onCloseWholeModal} />}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal dataId={dataId} chatTime={chatTime} onClose={onCloseWholeModal} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export const VariableInputItem = ({
|
||||
value: item.value
|
||||
}))}
|
||||
value={value}
|
||||
onchange={(e) => setValue(`variables.${item.key}`, e)}
|
||||
onChange={(e) => setValue(`variables.${item.key}`, e)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -12,12 +12,18 @@ const RenderResponseDetail = () => {
|
||||
const isChatting = useContextSelector(PluginRunContext, (v) => v.isChatting);
|
||||
|
||||
const responseData = chatRecords?.[1]?.responseData || [];
|
||||
const chatTime = new Date();
|
||||
|
||||
return isChatting ? (
|
||||
<>{t('chat:in_progress')}</>
|
||||
) : (
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
<ResponseBox useMobile={true} response={responseData} dataId={chatRecords?.[1]?.dataId} />
|
||||
<ResponseBox
|
||||
useMobile={true}
|
||||
response={responseData}
|
||||
dataId={chatRecords?.[1]?.dataId}
|
||||
chatTime={chatTime}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -162,7 +162,7 @@ const RenderPluginInput = ({
|
||||
}
|
||||
if (inputType === FlowNodeInputTypeEnum.select && input.list) {
|
||||
return (
|
||||
<MySelect list={input.list} value={value} onchange={onChange} isDisabled={isDisabled} />
|
||||
<MySelect list={input.list} value={value} onChange={onChange} isDisabled={isDisabled} />
|
||||
);
|
||||
}
|
||||
if (inputType === FlowNodeInputTypeEnum.fileSelect) {
|
||||
@@ -179,7 +179,7 @@ const RenderPluginInput = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={onChange}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,19 @@ import React from 'react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d';
|
||||
|
||||
const ChatBoxDivider = ({ icon, text }: { icon: IconNameType; text: string }) => {
|
||||
const ChatBoxDivider = ({
|
||||
icon,
|
||||
text,
|
||||
iconColor
|
||||
}: {
|
||||
icon: IconNameType;
|
||||
text: string;
|
||||
iconColor?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} py={2} gap={2}>
|
||||
<MyIcon name={icon} w={'14px'} color={'myGray.900'} />
|
||||
<MyIcon name={icon} w={'14px'} color={iconColor || 'myGray.900'} />
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{text}
|
||||
</Box>
|
||||
|
||||
@@ -299,6 +299,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
<MyNumberInput
|
||||
min={input.min}
|
||||
max={input.max}
|
||||
defaultValue={input.defaultValue}
|
||||
isDisabled={interactive.params.submitted}
|
||||
bg={'white'}
|
||||
register={register}
|
||||
@@ -321,7 +322,7 @@ const RenderUserFormInteractive = React.memo(function RenderFormInput({
|
||||
list={input.list}
|
||||
value={value}
|
||||
isDisabled={interactive.params.submitted}
|
||||
onchange={(e) => setValue(input.label, e)}
|
||||
onChange={(e) => setValue(input.label, e)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { QuoteList } from '../ChatContainer/ChatBox/components/QuoteModal';
|
||||
import QuoteList from '../ChatContainer/ChatBox/components/QuoteList';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { formatNumber } from '@fastgpt/global/common/math/tools';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
@@ -32,11 +32,13 @@ type sideTabItemType = {
|
||||
export const WholeResponseContent = ({
|
||||
activeModule,
|
||||
hideTabs,
|
||||
dataId
|
||||
dataId,
|
||||
chatTime
|
||||
}: {
|
||||
activeModule: ChatHistoryItemResType;
|
||||
hideTabs?: boolean;
|
||||
dataId?: string;
|
||||
chatTime?: Date;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -226,10 +228,23 @@ export const WholeResponseContent = ({
|
||||
{activeModule?.searchMode && (
|
||||
<Row
|
||||
label={t('common:core.dataset.search.search mode')}
|
||||
// @ts-ignore
|
||||
value={t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
rawDom={
|
||||
<Flex border={'base'} borderRadius={'md'} p={2}>
|
||||
<Box>
|
||||
{/* @ts-ignore */}
|
||||
{t(DatasetSearchModeMap[activeModule.searchMode]?.title)}
|
||||
</Box>
|
||||
{activeModule.embeddingWeight && (
|
||||
<>{`(${t('chat:response_hybrid_weight', {
|
||||
emb: activeModule.embeddingWeight,
|
||||
text: 1 - activeModule.embeddingWeight
|
||||
})})`}</>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Row
|
||||
label={t('common:core.chat.response.module similarity')}
|
||||
value={activeModule?.similarity}
|
||||
@@ -237,7 +252,19 @@ export const WholeResponseContent = ({
|
||||
<Row label={t('common:core.chat.response.module limit')} value={activeModule?.limit} />
|
||||
<Row
|
||||
label={t('common:core.chat.response.search using reRank')}
|
||||
value={`${activeModule?.searchUsingReRank}`}
|
||||
rawDom={
|
||||
<Box border={'base'} borderRadius={'md'} p={2}>
|
||||
{activeModule?.searchUsingReRank ? (
|
||||
activeModule?.rerankModel ? (
|
||||
<Box>{`${activeModule.rerankModel}: ${activeModule.rerankWeight}`}</Box>
|
||||
) : (
|
||||
'True'
|
||||
)
|
||||
) : (
|
||||
`False`
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
{activeModule.queryExtensionResult && (
|
||||
<>
|
||||
@@ -263,7 +290,7 @@ export const WholeResponseContent = ({
|
||||
{activeModule.quoteList && activeModule.quoteList.length > 0 && (
|
||||
<Row
|
||||
label={t('common:core.chat.response.module quoteList')}
|
||||
rawDom={<QuoteList chatItemId={dataId} rawSearch={activeModule.quoteList} />}
|
||||
rawDom={<QuoteList chatItemDataId={dataId} rawSearch={activeModule.quoteList} />}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -562,11 +589,13 @@ const SideTabItem = ({
|
||||
export const ResponseBox = React.memo(function ResponseBox({
|
||||
response,
|
||||
dataId,
|
||||
chatTime,
|
||||
hideTabs = false,
|
||||
useMobile = false
|
||||
}: {
|
||||
response: ChatHistoryItemResType[];
|
||||
dataId?: string;
|
||||
chatTime: Date;
|
||||
hideTabs?: boolean;
|
||||
useMobile?: boolean;
|
||||
}) {
|
||||
@@ -689,7 +718,12 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex={'5 0 0'} w={0} height={'100%'}>
|
||||
<WholeResponseContent dataId={dataId} activeModule={activeModule} hideTabs={hideTabs} />
|
||||
<WholeResponseContent
|
||||
dataId={dataId}
|
||||
activeModule={activeModule}
|
||||
hideTabs={hideTabs}
|
||||
chatTime={chatTime}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
) : (
|
||||
@@ -753,6 +787,7 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
dataId={dataId}
|
||||
activeModule={activeModule}
|
||||
hideTabs={hideTabs}
|
||||
chatTime={chatTime}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
@@ -763,7 +798,15 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
);
|
||||
});
|
||||
|
||||
const WholeResponseModal = ({ onClose, dataId }: { onClose: () => void; dataId: string }) => {
|
||||
const WholeResponseModal = ({
|
||||
onClose,
|
||||
dataId,
|
||||
chatTime
|
||||
}: {
|
||||
onClose: () => void;
|
||||
dataId: string;
|
||||
chatTime: Date;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { getHistoryResponseData } = useContextSelector(ChatBoxContext, (v) => v);
|
||||
@@ -792,7 +835,7 @@ const WholeResponseModal = ({ onClose, dataId }: { onClose: () => void; dataId:
|
||||
}
|
||||
>
|
||||
{!!response?.length ? (
|
||||
<ResponseBox response={response} dataId={dataId} />
|
||||
<ResponseBox response={response} dataId={dataId} chatTime={chatTime} />
|
||||
) : (
|
||||
<EmptyTip text={t('chat:no_workflow_response')} />
|
||||
)}
|
||||
|
||||
@@ -3,11 +3,10 @@ import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import React from 'react';
|
||||
import { DatasetTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const DatasetTypeTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps) => {
|
||||
const { datasetT } = useI18n();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const item = DatasetTypeMap[type] || DatasetTypeMap['dataset'];
|
||||
|
||||
return (
|
||||
@@ -24,8 +23,7 @@ const DatasetTypeTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & Fle
|
||||
{...props}
|
||||
>
|
||||
<MyIcon name={item.icon as any} w={'16px'} mr={2} color={'myGray.400'} />
|
||||
{/* @ts-ignore */}
|
||||
<Box>{datasetT(item.label)}</Box>
|
||||
<Box>{t(item.label as any)}</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,8 +14,8 @@ import Markdown from '@/components/Markdown';
|
||||
|
||||
const InputDataModal = dynamic(() => import('@/pageComponents/dataset/detail/InputDataModal'));
|
||||
|
||||
type ScoreItemType = SearchDataResponseItemType['score'][0];
|
||||
const scoreTheme: Record<
|
||||
export type ScoreItemType = SearchDataResponseItemType['score'][0];
|
||||
export const scoreTheme: Record<
|
||||
string,
|
||||
{
|
||||
color: string;
|
||||
@@ -44,6 +44,47 @@ const scoreTheme: Record<
|
||||
}
|
||||
};
|
||||
|
||||
export const formatScore = (score: ScoreItemType[]) => {
|
||||
if (!Array.isArray(score)) {
|
||||
return {
|
||||
primaryScore: undefined,
|
||||
secondaryScore: []
|
||||
};
|
||||
}
|
||||
|
||||
// rrf -> rerank -> embedding -> fullText 优先级
|
||||
let rrfScore: ScoreItemType | undefined = undefined;
|
||||
let reRankScore: ScoreItemType | undefined = undefined;
|
||||
let embeddingScore: ScoreItemType | undefined = undefined;
|
||||
let fullTextScore: ScoreItemType | undefined = undefined;
|
||||
|
||||
score.forEach((item) => {
|
||||
if (item.type === SearchScoreTypeEnum.rrf) {
|
||||
rrfScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.reRank) {
|
||||
reRankScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.embedding) {
|
||||
embeddingScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.fullText) {
|
||||
fullTextScore = item;
|
||||
}
|
||||
});
|
||||
|
||||
const primaryScore = (rrfScore ||
|
||||
reRankScore ||
|
||||
embeddingScore ||
|
||||
fullTextScore) as unknown as ScoreItemType;
|
||||
const secondaryScore = [rrfScore, reRankScore, embeddingScore, fullTextScore].filter(
|
||||
// @ts-ignore
|
||||
(item) => item && primaryScore && item.type !== primaryScore.type
|
||||
) as unknown as ScoreItemType[];
|
||||
|
||||
return {
|
||||
primaryScore,
|
||||
secondaryScore
|
||||
};
|
||||
};
|
||||
|
||||
const QuoteItem = ({
|
||||
quoteItem,
|
||||
canViewSource,
|
||||
@@ -58,44 +99,7 @@ const QuoteItem = ({
|
||||
const [editInputData, setEditInputData] = useState<{ dataId: string; collectionId: string }>();
|
||||
|
||||
const score = useMemo(() => {
|
||||
if (!Array.isArray(quoteItem.score)) {
|
||||
return {
|
||||
primaryScore: undefined,
|
||||
secondaryScore: []
|
||||
};
|
||||
}
|
||||
|
||||
// rrf -> rerank -> embedding -> fullText 优先级
|
||||
let rrfScore: ScoreItemType | undefined = undefined;
|
||||
let reRankScore: ScoreItemType | undefined = undefined;
|
||||
let embeddingScore: ScoreItemType | undefined = undefined;
|
||||
let fullTextScore: ScoreItemType | undefined = undefined;
|
||||
|
||||
quoteItem.score.forEach((item) => {
|
||||
if (item.type === SearchScoreTypeEnum.rrf) {
|
||||
rrfScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.reRank) {
|
||||
reRankScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.embedding) {
|
||||
embeddingScore = item;
|
||||
} else if (item.type === SearchScoreTypeEnum.fullText) {
|
||||
fullTextScore = item;
|
||||
}
|
||||
});
|
||||
|
||||
const primaryScore = (rrfScore ||
|
||||
reRankScore ||
|
||||
embeddingScore ||
|
||||
fullTextScore) as unknown as ScoreItemType;
|
||||
const secondaryScore = [rrfScore, reRankScore, embeddingScore, fullTextScore].filter(
|
||||
// @ts-ignore
|
||||
(item) => item && primaryScore && item.type !== primaryScore.type
|
||||
) as unknown as ScoreItemType[];
|
||||
|
||||
return {
|
||||
primaryScore,
|
||||
secondaryScore
|
||||
};
|
||||
return formatScore(quoteItem.score);
|
||||
}, [quoteItem.score]);
|
||||
|
||||
return (
|
||||
@@ -239,7 +243,7 @@ const QuoteItem = ({
|
||||
color={'primary.500'}
|
||||
href={`/dataset/detail?datasetId=${quoteItem.datasetId}¤tTab=dataCard&collectionId=${quoteItem.collectionId}`}
|
||||
>
|
||||
{t('common:core.dataset.Go Dataset')}
|
||||
{t('chat:to_dataset')}
|
||||
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@@ -23,7 +23,7 @@ const RawSourceBox = ({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
chatItemDataId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
@@ -40,7 +40,7 @@ const RawSourceBox = ({
|
||||
collectionId,
|
||||
appId,
|
||||
chatId,
|
||||
chatItemId,
|
||||
chatItemDataId,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
teamId,
|
||||
|
||||
@@ -164,7 +164,7 @@ const LafAccountModal = ({
|
||||
}
|
||||
placeholder={t('common:plugin.App')}
|
||||
value={watch('appid')}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('appid', e);
|
||||
}}
|
||||
{...(register('appid'), { required: true })}
|
||||
|
||||
@@ -49,7 +49,7 @@ const DefaultPermissionList = ({
|
||||
<MySelect
|
||||
list={defaultPermissionSelectList}
|
||||
value={per}
|
||||
onchange={(per) => {
|
||||
onChange={(per) => {
|
||||
if (isInheritPermission && hasParent) {
|
||||
openConfirm(
|
||||
() => onRequestChange(per),
|
||||
|
||||
@@ -79,7 +79,7 @@ const UpdateContactModal = ({
|
||||
title={
|
||||
mode === 'notification_account'
|
||||
? t('common:support.user.info.notification_receiving_hint')
|
||||
: t('account_info:contact')
|
||||
: t('common:contact_way')
|
||||
}
|
||||
>
|
||||
<ModalBody px={10}>
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { Button, ModalFooter, ModalBody, Flex, Box, useTheme } from '@chakra-ui/react';
|
||||
import { getTeamList, updateInviteResult } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
const UpdateInviteModal = () => {
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { initUserInfo } = useUserStore();
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({});
|
||||
|
||||
const { data: inviteList = [], run: fetchInviteList } = useRequest2(
|
||||
async () => (feConfigs.isPlus ? getTeamList(TeamMemberStatusEnum.waiting) : []),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { runAsync: onAccept, loading: isLoadingAccept } = useRequest2(updateInviteResult, {
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common:user.team.invite.Accepted')
|
||||
});
|
||||
fetchInviteList();
|
||||
initUserInfo();
|
||||
}
|
||||
});
|
||||
const { runAsync: onReject, loading: isLoadingReject } = useRequest2(updateInviteResult, {
|
||||
onSuccess() {
|
||||
toast({
|
||||
status: 'success',
|
||||
title: t('common:user.team.invite.Reject')
|
||||
});
|
||||
fetchInviteList();
|
||||
initUserInfo();
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={inviteList && inviteList.length > 0}
|
||||
iconSrc="/imgs/modal/team.svg"
|
||||
title={
|
||||
<Box>
|
||||
<Box>{t('common:user.team.Processing invitations')}</Box>
|
||||
<Box fontWeight={'normal'} fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('common:user.team.Processing invitations Tips', { amount: inviteList?.length })}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw', '500px']}
|
||||
>
|
||||
<ModalBody>
|
||||
{inviteList?.map((item) => (
|
||||
<Flex
|
||||
key={item.teamId}
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
_notFirst={{
|
||||
mt: 3
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.avatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{item.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={'solid'}
|
||||
colorScheme="green"
|
||||
isLoading={isLoadingAccept}
|
||||
onClick={() => {
|
||||
openConfirm(
|
||||
() =>
|
||||
onAccept({
|
||||
tmbId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.active
|
||||
}),
|
||||
undefined,
|
||||
t('common:user.team.invite.Accept Confirm')
|
||||
)();
|
||||
}}
|
||||
>
|
||||
{t('common:user.team.invite.accept')}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
ml={2}
|
||||
variant={'solid'}
|
||||
colorScheme="red"
|
||||
isLoading={isLoadingReject}
|
||||
onClick={() => {
|
||||
openConfirm(
|
||||
() =>
|
||||
onReject({
|
||||
tmbId: item.tmbId,
|
||||
status: TeamMemberStatusEnum.reject
|
||||
}),
|
||||
undefined,
|
||||
t('common:user.team.invite.Reject Confirm')
|
||||
)();
|
||||
}}
|
||||
>
|
||||
{t('common:user.team.invite.reject')}
|
||||
</Button>
|
||||
</Flex>
|
||||
))}
|
||||
</ModalBody>
|
||||
<ModalFooter justifyContent={'center'}>
|
||||
<Box>{t('common:user.team.invite.Deal Width Footer Tip')}</Box>
|
||||
</ModalFooter>
|
||||
|
||||
<ConfirmModal />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UpdateInviteModal);
|
||||
@@ -40,12 +40,16 @@ export const defaultChannel: ChannelInfoType = {
|
||||
priority: 0
|
||||
};
|
||||
|
||||
export const aiproxyIdMap: Record<number, { label: string; provider: ModelProviderIdType }> = {
|
||||
export const aiproxyIdMap: Record<
|
||||
number,
|
||||
{ label: string; provider: ModelProviderIdType; avatar?: string }
|
||||
> = {
|
||||
1: {
|
||||
label: 'OpenAI',
|
||||
provider: 'OpenAI'
|
||||
},
|
||||
3: {
|
||||
avatar: 'model/azure',
|
||||
label: i18nT('account_model:azure'),
|
||||
provider: 'OpenAI'
|
||||
},
|
||||
@@ -124,5 +128,28 @@ export const aiproxyIdMap: Record<number, { label: string; provider: ModelProvid
|
||||
30: {
|
||||
label: 'Ollama',
|
||||
provider: 'Ollama'
|
||||
},
|
||||
23: {
|
||||
label: 'OneAPI',
|
||||
provider: 'Hunyuan'
|
||||
},
|
||||
44: {
|
||||
label: 'doubao audio',
|
||||
provider: 'Doubao'
|
||||
},
|
||||
33: {
|
||||
label: 'AWS',
|
||||
provider: 'Other',
|
||||
avatar: 'model/aws'
|
||||
},
|
||||
35: {
|
||||
label: 'Cohere',
|
||||
provider: 'Other',
|
||||
avatar: 'model/cohere'
|
||||
},
|
||||
37: {
|
||||
label: 'Cloudflare',
|
||||
provider: 'Other',
|
||||
avatar: 'model/cloudflare'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
EmbeddingModelItemType,
|
||||
AudioSpeechModels,
|
||||
STTModelType,
|
||||
ReRankModelItemType
|
||||
RerankModelItemType
|
||||
} from '@fastgpt/global/core/ai/model.d';
|
||||
|
||||
import type { FastGPTFeConfigsType } from '@fastgpt/global/common/system/types/index.d';
|
||||
|
||||
@@ -63,8 +63,13 @@ export type SearchTestProps = {
|
||||
text: string;
|
||||
[NodeInputKeyEnum.datasetSimilarity]?: number;
|
||||
[NodeInputKeyEnum.datasetMaxTokens]?: number;
|
||||
|
||||
[NodeInputKeyEnum.datasetSearchMode]?: `${DatasetSearchModeEnum}`;
|
||||
[NodeInputKeyEnum.datasetSearchEmbeddingWeight]?: number;
|
||||
|
||||
[NodeInputKeyEnum.datasetSearchUsingReRank]?: boolean;
|
||||
[NodeInputKeyEnum.datasetSearchRerankModel]?: string;
|
||||
[NodeInputKeyEnum.datasetSearchRerankWeight]?: number;
|
||||
|
||||
[NodeInputKeyEnum.datasetSearchUsingExtensionQuery]?: boolean;
|
||||
[NodeInputKeyEnum.datasetSearchExtensionModel]?: string;
|
||||
|
||||
@@ -39,5 +39,6 @@ export type DatasetDataListItemType = {
|
||||
q: string; // embedding content
|
||||
a: string; // bonus content
|
||||
chunkIndex?: number;
|
||||
updated?: boolean;
|
||||
// indexes: DatasetDataSchemaType['indexes'];
|
||||
};
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, ButtonProps, Flex } from '@chakra-ui/react';
|
||||
import { Box, ButtonProps } from '@chakra-ui/react';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { getTeamList, putSwitchTeam } from '@/web/support/user/team/api';
|
||||
import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const TeamSelector = ({
|
||||
@@ -21,7 +19,7 @@ const TeamSelector = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { userInfo, initUserInfo } = useUserStore();
|
||||
const { userInfo } = useUserStore();
|
||||
const { setLoading } = useSystemStore();
|
||||
|
||||
const { data: myTeams = [] } = useRequest2(() => getTeamList(TeamMemberStatusEnum.active), {
|
||||
@@ -33,12 +31,11 @@ const TeamSelector = ({
|
||||
async (teamId: string) => {
|
||||
setLoading(true);
|
||||
await putSwitchTeam(teamId);
|
||||
return initUserInfo();
|
||||
},
|
||||
{
|
||||
onFinally: () => {
|
||||
router.reload();
|
||||
setLoading(false);
|
||||
onChange?.();
|
||||
},
|
||||
errorToast: t('common:user.team.Switch Team Failed')
|
||||
}
|
||||
@@ -46,48 +43,21 @@ const TeamSelector = ({
|
||||
|
||||
const teamList = useMemo(() => {
|
||||
return myTeams.map((team) => ({
|
||||
label: (
|
||||
<Flex
|
||||
key={team.teamId}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
cursor={'default'}
|
||||
gap={3}
|
||||
onClick={() => onSwitchTeam(team.teamId)}
|
||||
_hover={{
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Avatar src={team.avatar} w={['1.25rem', '1.375rem']} />
|
||||
<Box flex={'1 0 0'} w={0} className="textEllipsis" fontSize={'sm'}>
|
||||
{team.teamName}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
icon: team.avatar,
|
||||
iconSize: '1.25rem',
|
||||
label: team.teamName,
|
||||
value: team.teamId
|
||||
}));
|
||||
}, [myTeams, onSwitchTeam]);
|
||||
}, [myTeams]);
|
||||
|
||||
const formatTeamList = useMemo(() => {
|
||||
return [
|
||||
...(showManage
|
||||
? [
|
||||
{
|
||||
label: (
|
||||
<Flex
|
||||
key={'manage'}
|
||||
alignItems={'center'}
|
||||
borderRadius={'md'}
|
||||
cursor={'pointer'}
|
||||
gap={3}
|
||||
onClick={() => router.push('/account/team')}
|
||||
>
|
||||
<MyIcon name="common/setting" w={['1.25rem', '1.375rem']} />
|
||||
<Box flex={'1 0 0'} w={0} className="textEllipsis" fontSize={'sm'}>
|
||||
{t('user:manage_team')}
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
icon: 'common/setting',
|
||||
iconSize: '1.25rem',
|
||||
label: t('user:manage_team'),
|
||||
value: 'manage',
|
||||
showBorder: true
|
||||
}
|
||||
@@ -95,11 +65,24 @@ const TeamSelector = ({
|
||||
: []),
|
||||
...teamList
|
||||
];
|
||||
}, [showManage, t, teamList, router]);
|
||||
}, [showManage, t, teamList]);
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
if (value === 'manage') {
|
||||
router.push('/account/team');
|
||||
} else {
|
||||
onSwitchTeam(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box w={'100%'}>
|
||||
<MySelect {...props} value={userInfo?.team?.teamId} list={formatTeamList} />
|
||||
<MySelect
|
||||
{...props}
|
||||
value={userInfo?.team?.teamId}
|
||||
list={formatTeamList}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -111,7 +111,7 @@ const BillTable = () => {
|
||||
list={billTypeList}
|
||||
value={billType}
|
||||
size={'sm'}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setBillType(e);
|
||||
}}
|
||||
w={'130px'}
|
||||
@@ -220,13 +220,7 @@ function BillDetailModal({ bill, onClose }: { bill: BillSchemaType; onClose: ()
|
||||
{bill.metadata.payWay === 'balance' ? (
|
||||
t('user:bill.not_need_invoice')
|
||||
) : (
|
||||
<Box>
|
||||
{
|
||||
(bill.metadata.payWay = bill.hasInvoice
|
||||
? t('account_bill:yes')
|
||||
: t('account_bill:no'))
|
||||
}
|
||||
</Box>
|
||||
<Box>{bill.hasInvoice ? t('account_bill:yes') : t('account_bill:no')}</Box>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
@@ -213,7 +213,7 @@ export const ModelEditModal = ({
|
||||
<Td textAlign={'right'}>
|
||||
<MySelect
|
||||
value={provider}
|
||||
onchange={(value) => setValue('provider', value)}
|
||||
onChange={(value) => setValue('provider', value)}
|
||||
list={providerList.current}
|
||||
{...InputStyles}
|
||||
/>
|
||||
|
||||
@@ -79,7 +79,7 @@ const EditChannelModal = ({
|
||||
order: provider.order,
|
||||
defaultBaseUrl: value.defaultBaseUrl,
|
||||
keyHelp: value.keyHelp,
|
||||
icon: provider.avatar,
|
||||
icon: mapData?.avatar ?? provider.avatar,
|
||||
label: t(mapData.label as any),
|
||||
value: Number(key)
|
||||
};
|
||||
@@ -90,6 +90,7 @@ const EditChannelModal = ({
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const selectedProvider = useMemo(() => {
|
||||
const res = providerList.find((item) => item.value === providerType);
|
||||
return res;
|
||||
@@ -193,7 +194,7 @@ const EditChannelModal = ({
|
||||
placeholder={t('account_model:select_provider_placeholder')}
|
||||
value={providerType}
|
||||
isSearch
|
||||
onchange={(val) => {
|
||||
onChange={(val) => {
|
||||
setValue('type', val);
|
||||
}}
|
||||
/>
|
||||
@@ -332,6 +333,8 @@ const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const onclickItem = useCallback(
|
||||
(val: string) => {
|
||||
if (value.includes(val)) {
|
||||
@@ -342,12 +345,11 @@ const MultipleSelect = ({ value = [], list = [], onSelect }: SelectProps) => {
|
||||
top: BoxRef.current.scrollHeight
|
||||
});
|
||||
}
|
||||
setSearch('');
|
||||
},
|
||||
[value, onSelect]
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const filterUnSelected = useMemo(() => {
|
||||
return list
|
||||
.filter((item) => !value.includes(item.value))
|
||||
|
||||
@@ -25,6 +25,7 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { batchRun } from '@fastgpt/global/common/system/utils';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
type ModelTestItem = {
|
||||
label: React.ReactNode;
|
||||
@@ -34,7 +35,15 @@ type ModelTestItem = {
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void }) => {
|
||||
const ModelTest = ({
|
||||
channelId,
|
||||
models,
|
||||
onClose
|
||||
}: {
|
||||
channelId: number;
|
||||
models: string[];
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [testModelList, setTestModelList] = useState<ModelTestItem[]>([]);
|
||||
@@ -57,6 +66,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
colorSchema: 'red'
|
||||
}
|
||||
});
|
||||
|
||||
const { loading: loadingModels } = useRequest2(getSystemModelList, {
|
||||
manual: false,
|
||||
refreshDeps: [models],
|
||||
@@ -95,7 +105,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
);
|
||||
const start = Date.now();
|
||||
try {
|
||||
await getTestModel(model);
|
||||
await getTestModel({ model, channelId });
|
||||
const duration = Date.now() - start;
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
@@ -134,13 +144,47 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
refreshDeps: [testModelList]
|
||||
}
|
||||
);
|
||||
const { runAsync: onTestOneModel, loading: testingOneModel } = useRequest2(
|
||||
async (model: string) => {
|
||||
const start = Date.now();
|
||||
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'running', message: '' } : item
|
||||
)
|
||||
);
|
||||
|
||||
try {
|
||||
await getTestModel({ model, channelId });
|
||||
const duration = Date.now() - start;
|
||||
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'success', duration: duration / 1000 } : item
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
setTestModelList((prev) =>
|
||||
prev.map((item) =>
|
||||
item.model === model ? { ...item, status: 'error', message: getErrText(error) } : item
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: true
|
||||
}
|
||||
);
|
||||
|
||||
const isTestLoading = testingOneModel || isTesting;
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
iconSrc={'core/chat/sendLight'}
|
||||
isLoading={loadingModels}
|
||||
title={t('account_model:model_test')}
|
||||
w={'600px'}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '1090px']}
|
||||
isOpen
|
||||
>
|
||||
<ModalBody>
|
||||
@@ -148,8 +192,10 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{t('account_model:model')}</Th>
|
||||
<Th>{t('account_model:model_name')}</Th>
|
||||
<Th>{t('account:model.model_id')}</Th>
|
||||
<Th>{t('account_model:channel_status')}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -158,6 +204,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
return (
|
||||
<Tr key={item.model}>
|
||||
<Td>{item.label}</Td>
|
||||
<Td>{item.model}</Td>
|
||||
<Td>
|
||||
<Flex alignItems={'center'}>
|
||||
<MyTag mr={1} type="borderSolid" colorSchema={data.colorSchema as any}>
|
||||
@@ -173,6 +220,16 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
)}
|
||||
</Flex>
|
||||
</Td>
|
||||
<Td>
|
||||
<MyIconButton
|
||||
isLoading={isTestLoading}
|
||||
icon={'core/chat/sendLight'}
|
||||
tip={t('account:model.test_model')}
|
||||
onClick={() => {
|
||||
onTestOneModel(item.model);
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
@@ -184,7 +241,7 @@ const ModelTest = ({ models, onClose }: { models: string[]; onClose: () => void
|
||||
<Button mr={4} variant={'whiteBase'} onClick={onClose}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={isTesting} variant={'primary'} onClick={onStartTest}>
|
||||
<Button isLoading={isTestLoading} variant={'primary'} onClick={onStartTest}>
|
||||
{t('account_model:start_test', { num: testModelList.length })}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
||||
@@ -74,7 +74,7 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
}
|
||||
});
|
||||
|
||||
const [testModels, setTestModels] = useState<string[]>();
|
||||
const [modelTestData, setTestModelData] = useState<{ channelId: number; models: string[] }>();
|
||||
|
||||
const isLoading =
|
||||
loadingChannelList ||
|
||||
@@ -165,7 +165,11 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
{
|
||||
icon: 'core/chat/sendLight',
|
||||
label: t('account_model:model_test'),
|
||||
onClick: () => setTestModels(item.models)
|
||||
onClick: () =>
|
||||
setTestModelData({
|
||||
channelId: item.id,
|
||||
models: item.models
|
||||
})
|
||||
},
|
||||
...(item.status === ChannelStatusEnum.ChannelStatusEnabled
|
||||
? [
|
||||
@@ -222,7 +226,9 @@ const ChannelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
onSuccess={refreshChannelList}
|
||||
/>
|
||||
)}
|
||||
{!!testModels && <ModelTest models={testModels} onClose={() => setTestModels(undefined)} />}
|
||||
{!!modelTestData && (
|
||||
<ModelTest {...modelTestData} onClose={() => setTestModelData(undefined)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -206,7 +206,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
list={channelList}
|
||||
placeholder={t('account_model:select_channel')}
|
||||
value={filterProps.channelId}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, channelId: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -219,7 +219,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
list={modelList}
|
||||
placeholder={t('account_model:select_model')}
|
||||
value={filterProps.model}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, model: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -234,7 +234,7 @@ const ChannelLog = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
{ label: t('common:common.failed'), value: 'error' }
|
||||
]}
|
||||
value={filterProps.code_type}
|
||||
onchange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
onChange={(val) => setFilterProps({ ...filterProps, code_type: val })}
|
||||
/>
|
||||
</Box>
|
||||
</HStack>
|
||||
@@ -298,11 +298,15 @@ const LogDetail = ({ data, onClose }: { data: LogDetailType; onClose: () => void
|
||||
const { data: detailData } = useRequest2(
|
||||
async () => {
|
||||
if (data.code === 200) return data;
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
try {
|
||||
const res = await getLogDetail(data.id);
|
||||
return {
|
||||
...res,
|
||||
...data
|
||||
};
|
||||
} catch (error) {
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
|
||||
@@ -280,6 +280,10 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
|
||||
isCustom: true,
|
||||
isActive: true,
|
||||
|
||||
isDefault: false,
|
||||
isDefaultDatasetTextModel: false,
|
||||
isDefaultDatasetImageModel: false,
|
||||
// @ts-ignore
|
||||
type
|
||||
});
|
||||
@@ -326,7 +330,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
w={'200px'}
|
||||
bg={'myGray.50'}
|
||||
value={provider}
|
||||
onchange={setProvider}
|
||||
onChange={setProvider}
|
||||
list={filterProviderList}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -338,7 +342,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
w={'150px'}
|
||||
bg={'myGray.50'}
|
||||
value={modelType}
|
||||
onchange={setModelType}
|
||||
onChange={setModelType}
|
||||
list={selectModelTypeList.current}
|
||||
/>
|
||||
</HStack>
|
||||
@@ -436,7 +440,7 @@ const ModelTable = ({ Tab }: { Tab: React.ReactNode }) => {
|
||||
<MyIconButton
|
||||
icon={'core/chat/sendLight'}
|
||||
tip={t('account:model.test_model')}
|
||||
onClick={() => onTestModel(item.model)}
|
||||
onClick={() => onTestModel({ model: item.model })}
|
||||
/>
|
||||
<MyIconButton
|
||||
icon={'common/settingLight'}
|
||||
@@ -597,7 +601,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
llm: llmModelList.find((item) => item.model === e)
|
||||
@@ -616,7 +620,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
embedding: embeddingModelList.find((item) => item.model === e)
|
||||
@@ -635,7 +639,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
tts: ttsModelList.find((item) => item.model === e)
|
||||
@@ -654,7 +658,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
stt: sttModelList.find((item) => item.model === e)
|
||||
@@ -673,7 +677,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
rerank: reRankModelList.find((item) => item.model === e)
|
||||
@@ -696,7 +700,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
datasetTextLLM: datasetModelList.find((item) => item.model === e)
|
||||
@@ -718,7 +722,7 @@ const DefaultModelModal = ({
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setDefaultData((state) => ({
|
||||
...state,
|
||||
datasetImageLLM: vlmModelList.find((item) => item.model === e)
|
||||
|
||||
@@ -142,7 +142,7 @@ function EditModal({
|
||||
width={'fit-content'}
|
||||
>
|
||||
<Icon name="common/info" w="1rem" />
|
||||
<Box width="fit-content">{t('account_info:please_bind_contact')}</Box>
|
||||
<Box width="fit-content">{t('account_team:please_bind_contact')}</Box>
|
||||
</HStack>
|
||||
);
|
||||
})()}
|
||||
|
||||
@@ -172,7 +172,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
</Td>
|
||||
<Td>
|
||||
{group.name === DefaultGroupName ? (
|
||||
<AvatarGroup avatars={members.map((v) => v.avatar)} groupId={group._id} />
|
||||
<AvatarGroup avatars={members.map((v) => v.avatar)} />
|
||||
) : hasGroupManagePer(group) ? (
|
||||
<MyTooltip label={t('account_team:manage_member')}>
|
||||
<Box cursor="pointer" onClick={() => onManageMember(group)}>
|
||||
@@ -180,7 +180,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
avatars={group.members.map(
|
||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
||||
)}
|
||||
groupId={group._id}
|
||||
/>
|
||||
</Box>
|
||||
</MyTooltip>
|
||||
@@ -189,7 +188,6 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
avatars={group.members.map(
|
||||
(v) => members.find((m) => m.tmbId === v.tmbId)?.avatar ?? ''
|
||||
)}
|
||||
groupId={group._id}
|
||||
/>
|
||||
)}
|
||||
</Td>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import { postCreateInvitationLink } from '@/web/support/user/team/api';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Grid,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Input,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalFooter,
|
||||
HStack
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
InvitationLinkCreateType,
|
||||
InvitationLinkExpiresType
|
||||
} from '@fastgpt/service/support/user/team/invitationLink/type';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
function CreateInvitationModal({ onClose }: { onClose: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const expiresOptions: Array<{ label: string; value: InvitationLinkExpiresType }> = [
|
||||
{ label: t('account_team:30mins'), value: '30m' }, // 30 mins
|
||||
{ label: t('account_team:7days'), value: '7d' }, // 7 days
|
||||
{ label: t('account_team:1year'), value: '1y' } // 1 year
|
||||
];
|
||||
|
||||
const { register, handleSubmit, watch, setValue } = useForm<InvitationLinkCreateType>({
|
||||
defaultValues: {
|
||||
description: '',
|
||||
expires: expiresOptions[1].value,
|
||||
usedTimesLimit: 1
|
||||
}
|
||||
});
|
||||
|
||||
const expires = watch('expires');
|
||||
const usedTimesLimit = watch('usedTimesLimit');
|
||||
|
||||
const { runAsync: createInvitationLink, loading } = useRequest2(postCreateInvitationLink, {
|
||||
manual: true,
|
||||
successToast: t('common:common.Create Success'),
|
||||
errorToast: t('common:common.Create Failed'),
|
||||
onFinally: () => onClose()
|
||||
});
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="common/addLight"
|
||||
iconColor="primary.500"
|
||||
title={<Box>{t('account_team:create_invitation_link')}</Box>}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Grid gap={6} templateColumns="max-content 1fr" alignItems="center">
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:invitation_link_description')}</FormLabel>
|
||||
<Input
|
||||
placeholder={t('account_team:invitation_link_description')}
|
||||
{...register('description', { required: true })}
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:expires')}</FormLabel>
|
||||
<MySelect
|
||||
list={expiresOptions}
|
||||
value={expires}
|
||||
onChange={(val) => setValue('expires', val)}
|
||||
minW="120px"
|
||||
/>
|
||||
</>
|
||||
|
||||
<>
|
||||
<FormLabel required={true}>{t('account_team:used_times_limit')}</FormLabel>
|
||||
<RadioGroup
|
||||
onChange={(val: '1' | '-1') => setValue('usedTimesLimit', Number(val) as 1 | -1)}
|
||||
value={String(usedTimesLimit)}
|
||||
>
|
||||
<HStack gap={6}>
|
||||
<Radio value="1">{t('account_team:1person')}</Radio>
|
||||
<Radio value="-1">{t('account_team:unlimited')}</Radio>
|
||||
</HStack>
|
||||
</RadioGroup>
|
||||
</>
|
||||
</Grid>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button isLoading={loading} onClick={onClose} variant="outline">
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button isLoading={loading} onClick={handleSubmit(createInvitationLink)} ml="4">
|
||||
{t('common:common.Confirm')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateInvitationModal;
|
||||
@@ -0,0 +1,77 @@
|
||||
import { getInvitationInfo, postAcceptInvitationLink } from '@/web/support/user/team/api';
|
||||
import { Box, Button, Flex, ModalBody, ModalCloseButton } from '@chakra-ui/react';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamContext } from '../context';
|
||||
|
||||
function Invite({ invitelinkid }: { invitelinkid: string }) {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { onSwitchTeam } = useContextSelector(TeamContext, (v) => v);
|
||||
|
||||
const onClose = () => {
|
||||
router.push('/account/team');
|
||||
};
|
||||
|
||||
const { data: invitationInfo } = useRequest2(() => getInvitationInfo(invitelinkid), {
|
||||
manual: false,
|
||||
onError: onClose
|
||||
});
|
||||
|
||||
const { runAsync: acceptInvitation, loading: accepting } = useRequest2(
|
||||
() => postAcceptInvitationLink(invitelinkid),
|
||||
{
|
||||
manual: true,
|
||||
successToast: t('common:common.Success'),
|
||||
onSuccess: async () => {
|
||||
onSwitchTeam(invitationInfo!.teamId);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return invitationInfo ? (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="support/user/usersLight"
|
||||
title={t('account_team:handle_invitation')}
|
||||
iconColor={'primary.600'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Flex
|
||||
key={invitationInfo._id}
|
||||
alignItems={'center'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
py={2}
|
||||
>
|
||||
<Avatar src={invitationInfo.teamAvatar} w={['16px', '23px']} />
|
||||
<Box mx={2}>{invitationInfo.teamName}</Box>
|
||||
<Box flex={1} />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={'solid'}
|
||||
colorScheme="green"
|
||||
onClick={acceptInvitation}
|
||||
isLoading={accepting}
|
||||
>
|
||||
{t('account_team:accept')}
|
||||
</Button>
|
||||
<Button size="sm" ml={2} variant="outline" onClick={onClose} isLoading={accepting}>
|
||||
{t('account_team:ignore')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</MyModal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default Invite;
|
||||
@@ -0,0 +1,278 @@
|
||||
import MemberTag from '@/components/support/user/team/Info/MemberTag';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getInvitationLinkList, putUpdateInvitationInfo } from '@/web/support/user/team/api';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Flex,
|
||||
Grid,
|
||||
HStack,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Table,
|
||||
TableContainer,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import AvatarGroup from '@fastgpt/web/components/common/Avatar/AvatarGroup';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import format from 'date-fns/format';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const CreateInvitationModal = dynamic(() => import('./CreateInvitationModal'));
|
||||
|
||||
const InviteModal = ({
|
||||
teamId,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
teamId: string;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data: invitationLinkList,
|
||||
loading: isLoadingLink,
|
||||
runAsync: refetchInvitationLinkList
|
||||
} = useRequest2(() => getInvitationLinkList(), {
|
||||
manual: false
|
||||
});
|
||||
|
||||
const { isOpen: isOpenCreate, onOpen: onOpenCreate, onClose: onCloseCreate } = useDisclosure();
|
||||
|
||||
const isLoading = isLoadingLink;
|
||||
const { copyData } = useCopyData();
|
||||
const { userInfo } = useUserStore();
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const onCopy = useCallback(
|
||||
(linkId: string) => {
|
||||
const url = location.origin + `/account/team?invitelinkid=${linkId}`;
|
||||
const teamName = userInfo?.team.teamName;
|
||||
const systemName = feConfigs.systemTitle;
|
||||
const userName = userInfo?.team.memberName;
|
||||
copyData(
|
||||
t('account_team:invitation_copy_link', {
|
||||
teamName,
|
||||
systemName,
|
||||
userName,
|
||||
url
|
||||
})
|
||||
);
|
||||
},
|
||||
[copyData]
|
||||
);
|
||||
|
||||
const { runAsync: onForbid, loading: forbiding } = useRequest2(
|
||||
(linkId: string) =>
|
||||
putUpdateInvitationInfo({
|
||||
linkId,
|
||||
forbidden: true
|
||||
}),
|
||||
{
|
||||
manual: true,
|
||||
onSuccess: refetchInvitationLinkList,
|
||||
successToast: t('account_team:forbid_success')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isLoading={isLoading}
|
||||
isOpen
|
||||
iconSrc="common/inviteLight"
|
||||
iconColor="primary.600"
|
||||
title={t('account_team:invite_member')}
|
||||
overflow={'unset'}
|
||||
onClose={onClose}
|
||||
w={'100%'}
|
||||
maxW={['90vw', '820px']}
|
||||
>
|
||||
<ModalBody maxH="500px">
|
||||
<Flex alignItems={'center'} justifyContent={'space-between'} mb={4}>
|
||||
<HStack>
|
||||
<Icon name="common/list" w="16px" />
|
||||
<Box ml="6px" fontSize="md">
|
||||
{t('account_team:invitation_link_list')}
|
||||
</Box>
|
||||
</HStack>
|
||||
<Button onClick={onOpenCreate}>{t('account_team:create_invitation_link')}</Button>
|
||||
</Flex>
|
||||
<TableContainer overflowY={'auto'}>
|
||||
<Table fontSize={'sm'} overflow={'unset'}>
|
||||
<Thead>
|
||||
<Tr bgColor={'white !important'}>
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:invitation_link_description')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:expires')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:used_times_limit')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:invited')}</Th>
|
||||
<Th bgColor="myGray.100" borderRightRadius="6px">
|
||||
{t('common:common.Action')}
|
||||
</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
{!!invitationLinkList?.length && (
|
||||
<Tbody overflow={'unset'}>
|
||||
{invitationLinkList?.map((item) => {
|
||||
const isForbidden = item.forbidden || new Date(item.expires) < new Date();
|
||||
return (
|
||||
<Tr key={item._id} overflow={'unset'}>
|
||||
<Td maxW="200px" minW="100px">
|
||||
{item.description}
|
||||
</Td>
|
||||
<Td>
|
||||
{isForbidden ? (
|
||||
<Tag colorSchema="gray">{t('account_team:has_forbidden')}</Tag>
|
||||
) : (
|
||||
format(new Date(item.expires), 'yyyy-MM-dd HH:mm')
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{item.usedTimesLimit === -1
|
||||
? t('account_team:unlimited')
|
||||
: item.usedTimesLimit}
|
||||
</Td>
|
||||
<Td>
|
||||
{item.members.length > 0 && (
|
||||
<MyPopover
|
||||
w="fit-content"
|
||||
Trigger={
|
||||
<Box
|
||||
borderRadius="md"
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'myGray.100' }}
|
||||
p="1.5"
|
||||
w="fit-content"
|
||||
>
|
||||
<AvatarGroup max={3} avatars={item.members.map((i) => i.avatar)} />
|
||||
</Box>
|
||||
}
|
||||
trigger="click"
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{() => (
|
||||
<Box py="4" maxH="200px" w="fit-content">
|
||||
<Flex mx="4" justifyContent="center" alignItems={'center'}>
|
||||
<Box>{t('account_team:has_invited')}</Box>
|
||||
<Box
|
||||
ml="auto"
|
||||
bg="myGray.200"
|
||||
px="2"
|
||||
borderRadius="md"
|
||||
fontSize="sm"
|
||||
>
|
||||
{item.members.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Divider my="2" mx="4" />
|
||||
<Grid
|
||||
w="fit-content"
|
||||
mt="2"
|
||||
gridRowGap="4"
|
||||
gridTemplateColumns="1fr 1fr"
|
||||
overflow="auto"
|
||||
alignItems="center"
|
||||
mx="4"
|
||||
>
|
||||
{item.members.map((member) => (
|
||||
<Box key={member.tmbId} justifySelf="start">
|
||||
<MemberTag name={member.name} avatar={member.avatar} />
|
||||
</Box>
|
||||
))}
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
{!isForbidden && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onCopy(item._id)}
|
||||
color="myGray.900"
|
||||
>
|
||||
<Icon name="common/link" w="16px" mr="1" />
|
||||
{t('account_team:copy_link')}
|
||||
</Button>
|
||||
<MyPopover
|
||||
placement="bottom-end"
|
||||
Trigger={
|
||||
<Button variant="outline" ml="10px" size="sm" color="myGray.900">
|
||||
<Icon name="common/lineStop" w="16px" mr="1" />
|
||||
{t('account_team:forbidden')}
|
||||
</Button>
|
||||
}
|
||||
closeOnBlur={true}
|
||||
>
|
||||
{({ onClose: onClosePopover }) => (
|
||||
<Box p={4}>
|
||||
<Box fontWeight={400} whiteSpace="pre-wrap">
|
||||
{t('account_team:forbid_hint')}
|
||||
</Box>
|
||||
<Flex gap={2} mt={2} justifyContent={'flex-end'}>
|
||||
<Button variant="outline" onClick={onClosePopover}>
|
||||
{t('common:common.Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
isLoading={forbiding}
|
||||
variant="outline"
|
||||
colorScheme="red"
|
||||
onClick={() => {
|
||||
onForbid(item._id);
|
||||
onClosePopover();
|
||||
}}
|
||||
>
|
||||
{t('account_team:confirm_forbidden')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
</>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
)}
|
||||
</Table>
|
||||
{!invitationLinkList?.length && <EmptyTip />}
|
||||
</TableContainer>
|
||||
</ModalBody>
|
||||
<ModalFooter justifyContent={'flex-start'}>
|
||||
<Tag colorSchema="blue" marginBlock="2">
|
||||
<Box>{t('account_team:invitation_link_auto_clean_hint')}</Box>
|
||||
</Tag>
|
||||
</ModalFooter>
|
||||
{isOpenCreate && (
|
||||
<CreateInvitationModal
|
||||
onClose={() => Promise.all([onCloseCreate(), refetchInvitationLinkList()])}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default InviteModal;
|
||||
@@ -1,90 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { ModalCloseButton, ModalBody, Box, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import TagTextarea from '@/components/common/Textarea/TagTextarea';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { postInviteTeamMember } from '@/web/support/user/team/api';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import type { InviteMemberResponse } from '@fastgpt/global/support/user/team/controller.d';
|
||||
|
||||
const InviteModal = ({
|
||||
teamId,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
teamId: string;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
title: t('user:team.Invite Member Result Tip'),
|
||||
showCancel: false
|
||||
});
|
||||
|
||||
const [inviteUsernames, setInviteUsernames] = useState<string[]>([]);
|
||||
|
||||
const { runAsync: onInvite, loading: isLoading } = useRequest2(
|
||||
() =>
|
||||
postInviteTeamMember({
|
||||
teamId,
|
||||
usernames: inviteUsernames
|
||||
}),
|
||||
{
|
||||
onSuccess(res: InviteMemberResponse) {
|
||||
onSuccess();
|
||||
openConfirm(
|
||||
() => onClose(),
|
||||
undefined,
|
||||
<Box whiteSpace={'pre-wrap'}>
|
||||
{t('user:team.Invite Member Success Tip', {
|
||||
success: res.invite.length,
|
||||
inValid: res.inValid.map((item) => item.username).join(', '),
|
||||
inTeam: res.inTeam.map((item) => item.username).join(', ')
|
||||
})}
|
||||
</Box>
|
||||
)();
|
||||
},
|
||||
errorToast: t('user:team.Invite Member Failed Tip')
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen
|
||||
iconSrc="common/inviteLight"
|
||||
iconColor="primary.600"
|
||||
title={
|
||||
<Box>
|
||||
<Box>{t('common:user.team.Invite Member')}</Box>
|
||||
<Box color={'myGray.500'} fontSize={'xs'} fontWeight={'normal'}>
|
||||
{t('common:user.team.Invite Member Tips')}
|
||||
</Box>
|
||||
</Box>
|
||||
}
|
||||
maxW={['90vw', '400px']}
|
||||
overflow={'unset'}
|
||||
>
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
<ModalBody>
|
||||
<Box mb={2}>{t('common:user.Account')}</Box>
|
||||
<TagTextarea defaultValues={inviteUsernames} onUpdate={setInviteUsernames} />
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<Button
|
||||
w={'100%'}
|
||||
h={'34px'}
|
||||
isDisabled={inviteUsernames.length === 0}
|
||||
isLoading={isLoading}
|
||||
onClick={onInvite}
|
||||
>
|
||||
{t('user:team.Confirm Invite')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
<ConfirmModal />
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default InviteModal;
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { delRemoveMember, updateStatus } from '@/web/support/user/team/api';
|
||||
import { delRemoveMember, postRestoreMember } from '@/web/support/user/team/api';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import Icon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
@@ -41,7 +41,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput';
|
||||
import { useState } from 'react';
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
|
||||
const InviteModal = dynamic(() => import('./InviteModal'));
|
||||
const InviteModal = dynamic(() => import('./Invite/InviteModal'));
|
||||
const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal'));
|
||||
|
||||
function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
@@ -118,7 +118,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
errorToast: t('account_team:sync_member_failed')
|
||||
});
|
||||
|
||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(updateStatus, {
|
||||
const { runAsync: onRestore, loading: isUpdateInvite } = useRequest2(postRestoreMember, {
|
||||
onSuccess() {
|
||||
refetchMembers();
|
||||
},
|
||||
@@ -175,22 +175,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
borderRadius={'md'}
|
||||
ml={3}
|
||||
leftIcon={<MyIcon name="common/inviteLight" w={'16px'} color={'white'} />}
|
||||
onClick={() => {
|
||||
if (
|
||||
teamPlanStatus?.standardConstants?.maxTeamMember &&
|
||||
teamPlanStatus.standardConstants.maxTeamMember <= members.length
|
||||
) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:user.team.Over Max Member Tip', {
|
||||
max: teamPlanStatus.standardConstants.maxTeamMember
|
||||
})
|
||||
});
|
||||
setNotSufficientModalType(TeamErrEnum.teamMemberOverSize);
|
||||
} else {
|
||||
onOpenInvite();
|
||||
}
|
||||
}}
|
||||
onClick={onOpenInvite}
|
||||
>
|
||||
{t('account_team:user_team_invite_member')}
|
||||
</Button>
|
||||
@@ -236,7 +221,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Th borderLeftRadius="6px" bgColor="myGray.100">
|
||||
{t('account_team:user_name')}
|
||||
</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:contact')}</Th>
|
||||
<Th bgColor="myGray.100">{t('common:contact_way')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:org')}</Th>
|
||||
<Th bgColor="myGray.100">{t('account_team:join_update_time')}</Th>
|
||||
<Th borderRightRadius="6px" bgColor="myGray.100">
|
||||
@@ -253,12 +238,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
<Avatar src={member.avatar} w={['18px', '22px']} borderRadius={'50%'} />
|
||||
<Box className={'textEllipsis'}>
|
||||
{member.memberName}
|
||||
{member.status === 'waiting' && (
|
||||
<Tag ml="2" colorSchema="yellow">
|
||||
{t('account_team:waiting')}
|
||||
</Tag>
|
||||
)}
|
||||
{member.status === 'leave' && (
|
||||
{member.status !== 'active' && (
|
||||
<Tag ml="2" colorSchema="gray">
|
||||
{t('account_team:leave')}
|
||||
</Tag>
|
||||
@@ -295,7 +275,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
{userInfo?.team.permission.hasManagePer &&
|
||||
member.role !== TeamMemberRoleEnum.owner &&
|
||||
member.tmbId !== userInfo?.team.tmbId &&
|
||||
(member.status !== TeamMemberStatusEnum.leave ? (
|
||||
(member.status === TeamMemberStatusEnum.active ? (
|
||||
<Icon
|
||||
name={'common/trash'}
|
||||
cursor={'pointer'}
|
||||
@@ -320,30 +300,28 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) {
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={'common/confirm/restoreTip'}
|
||||
cursor={'pointer'}
|
||||
w="1rem"
|
||||
p="1"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
color: 'primary.500',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
openRestoreMember(
|
||||
() =>
|
||||
onRestore({
|
||||
tmbId: member.tmbId,
|
||||
status: TeamMemberStatusEnum.active
|
||||
}),
|
||||
undefined,
|
||||
t('account_team:restore_tip', {
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
member.status === TeamMemberStatusEnum.forbidden && (
|
||||
<Icon
|
||||
name={'common/confirm/restoreTip'}
|
||||
cursor={'pointer'}
|
||||
w="1rem"
|
||||
p="1"
|
||||
borderRadius="sm"
|
||||
_hover={{
|
||||
color: 'primary.500',
|
||||
bgColor: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
openRestoreMember(
|
||||
() => onRestore(member.tmbId),
|
||||
undefined,
|
||||
t('account_team:restore_tip', {
|
||||
username: member.memberName
|
||||
})
|
||||
)();
|
||||
}}
|
||||
/>
|
||||
)
|
||||
))}
|
||||
</Td>
|
||||
</Tr>
|
||||
|
||||
@@ -101,6 +101,7 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode })
|
||||
const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2(
|
||||
async (teamId: string) => {
|
||||
await putSwitchTeam(teamId);
|
||||
refetchMembers();
|
||||
return initUserInfo();
|
||||
},
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ import ChatRecordContextProvider, {
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
|
||||
|
||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
||||
const ChatBox = dynamic(() => import('@/components/core/chat/ChatContainer/ChatBox'));
|
||||
@@ -37,6 +38,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
@@ -79,7 +82,7 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
right={0}
|
||||
h={['100%', '96%']}
|
||||
w={'100%'}
|
||||
maxW={['100%', '600px']}
|
||||
maxW={quoteData ? ['100%', '1080px'] : ['100%', '600px']}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'md'}
|
||||
@@ -148,26 +151,48 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
)}
|
||||
|
||||
{/* Chat container */}
|
||||
<Box pt={2} flex={'1 0 0'} h={0}>
|
||||
{isPlugin ? (
|
||||
<Box h={'100%'} overflow={'auto'}>
|
||||
<Flex pt={2} flex={'1 0 0'} h={0}>
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
{isPlugin ? (
|
||||
<Box px={5} py={2}>
|
||||
<PluginRunBox appId={appId} chatId={chatId} />
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
isReady
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatType="log"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{quoteData && (
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
mr={4}
|
||||
maxW={'460px'}
|
||||
h={'98%'}
|
||||
bg={'white'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
|
||||
}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<ChatQuoteList
|
||||
rawSearch={quoteData.rawSearch}
|
||||
metadata={quoteData.metadata}
|
||||
onClose={() => setQuoteData(undefined)}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<ChatBox
|
||||
isReady
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
feedbackType={'admin'}
|
||||
showMarkIcon
|
||||
showVoiceIcon={false}
|
||||
chatType="log"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
|
||||
<Box zIndex={2} position={'fixed'} top={0} left={0} bottom={0} right={0} onClick={onClose} />
|
||||
</>
|
||||
);
|
||||
@@ -189,6 +214,7 @@ const Render = (props: Props) => {
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={params}>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import ApiKeyTable from '@/components/support/apikey/Table';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
const API = ({ appId }: { appId: string }) => {
|
||||
const { publishT } = useI18n();
|
||||
return <ApiKeyTable tips={publishT('app_key_tips')} appId={appId} />;
|
||||
const { t } = useTranslation();
|
||||
return <ApiKeyTable tips={t('publish:app_key_tips')} appId={appId} />;
|
||||
};
|
||||
|
||||
export default API;
|
||||
|
||||
@@ -42,7 +42,6 @@ import { getDocPath } from '@/web/common/system/doc';
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
@@ -185,6 +184,7 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
||||
name: item.name,
|
||||
responseDetail: item.responseDetail ?? false,
|
||||
showRawSource: item.showRawSource ?? false,
|
||||
// showFullText: item.showFullText ?? false,
|
||||
showNodeStatus: item.showNodeStatus ?? false,
|
||||
limit: item.limit
|
||||
})
|
||||
@@ -270,7 +270,6 @@ function EditLinkModal({
|
||||
}) {
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const { publishT } = useI18n();
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
@@ -281,6 +280,7 @@ function EditLinkModal({
|
||||
});
|
||||
|
||||
const responseDetail = watch('responseDetail');
|
||||
// const showFullText = watch('showFullText');
|
||||
const showRawSource = watch('showRawSource');
|
||||
|
||||
const isEdit = useMemo(() => !!defaultData._id, [defaultData]);
|
||||
@@ -306,7 +306,7 @@ function EditLinkModal({
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
iconSrc="/imgs/modal/shareFill.svg"
|
||||
title={isEdit ? publishT('edit_link') : publishT('create_link')}
|
||||
title={isEdit ? t('publish:edit_link') : t('publish:create_link')}
|
||||
maxW={['90vw', '700px']}
|
||||
w={'100%'}
|
||||
h={['90vh', 'auto']}
|
||||
@@ -325,10 +325,10 @@ function EditLinkModal({
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<FormLabel flex={'0 0 90px'}>{t('common:Name')}</FormLabel>
|
||||
<Input
|
||||
placeholder={publishT('link_name')}
|
||||
placeholder={t('publish:link_name')}
|
||||
maxLength={20}
|
||||
{...register('name', {
|
||||
required: t('common:common.name_is_empty') || 'name_is_empty'
|
||||
required: t('common:common.name_is_empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -353,7 +353,7 @@ function EditLinkModal({
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
<FormLabel>QPM</FormLabel>
|
||||
<QuestionTip ml={1} label={publishT('qpm_tips')}></QuestionTip>
|
||||
<QuestionTip ml={1} label={t('publish:qpm_tips')}></QuestionTip>
|
||||
</Flex>
|
||||
<Input
|
||||
max={1000}
|
||||
@@ -361,7 +361,7 @@ function EditLinkModal({
|
||||
min: 0,
|
||||
max: 1000,
|
||||
valueAsNumber: true,
|
||||
required: publishT('qpm_is_empty') || ''
|
||||
required: t('publish:qpm_is_empty')
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
@@ -385,11 +385,11 @@ function EditLinkModal({
|
||||
|
||||
<Flex alignItems={'center'} mt={4}>
|
||||
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||
<FormLabel>{publishT('token_auth')}</FormLabel>
|
||||
<QuestionTip ml={1} label={publishT('token_auth_tips') || ''}></QuestionTip>
|
||||
<FormLabel>{t('publish:token_auth')}</FormLabel>
|
||||
<QuestionTip ml={1} label={t('publish:token_auth_tips')}></QuestionTip>
|
||||
</Flex>
|
||||
<Input
|
||||
placeholder={publishT('token_auth_tips') || ''}
|
||||
placeholder={t('publish:token_auth_tips')}
|
||||
fontSize={'sm'}
|
||||
{...register('limit.hookUrl')}
|
||||
/>
|
||||
@@ -400,7 +400,7 @@ function EditLinkModal({
|
||||
fontSize={'xs'}
|
||||
color={'myGray.500'}
|
||||
>
|
||||
{publishT('token_auth_use_cases')}
|
||||
{t('publish:token_auth_use_cases')}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
@@ -421,8 +421,39 @@ function EditLinkModal({
|
||||
label={t('common:support.outlink.share.Response Quote tips')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch {...register('responseDetail')} isChecked={responseDetail} />
|
||||
<Switch
|
||||
{...register('responseDetail', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
// setValue('showFullText', false);
|
||||
setValue('showRawSource', false);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={responseDetail}
|
||||
/>
|
||||
</Flex>
|
||||
{/* <Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.Chat_quote_reader')}</FormLabel>
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.outlink.share.Full_text tips')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('showFullText', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
} else {
|
||||
setValue('showRawSource', false);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={showFullText}
|
||||
/>
|
||||
</Flex> */}
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.show_complete_quote')}</FormLabel>
|
||||
@@ -436,6 +467,7 @@ function EditLinkModal({
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
// setValue('showFullText', true);
|
||||
}
|
||||
}
|
||||
})}
|
||||
|
||||
@@ -99,7 +99,7 @@ const OutLink = () => {
|
||||
if (!feConfigs.isPlus && config.isProFn) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: t('common:common.system.Commercial version function')
|
||||
title: t('common:commercial_function_tip')
|
||||
});
|
||||
} else {
|
||||
setLinkType(e as PublishChannelEnum);
|
||||
|
||||
@@ -7,24 +7,26 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useSafeState } from 'ahooks';
|
||||
import { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { AppContext } from '../context';
|
||||
import { useChatTest } from '../useChatTest';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import ChatItemContextProvider from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatItemContextProvider, { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
import ChatRecordContextProvider from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { cardStyles } from '../constants';
|
||||
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
|
||||
|
||||
type Props = { appForm: AppSimpleEditFormType };
|
||||
const ChatTest = ({ appForm }: Props) => {
|
||||
type Props = {
|
||||
appForm: AppSimpleEditFormType;
|
||||
setRenderEdit: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
const ChatTest = ({ appForm, setRenderEdit }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { appT } = useI18n();
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
// form2AppWorkflow dependent allDatasets
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const [workflowData, setWorkflowData] = useSafeState({
|
||||
nodes: appDetail.modules || [],
|
||||
@@ -33,9 +35,12 @@ const ChatTest = ({ appForm }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const { nodes, edges } = form2AppWorkflow(appForm, t);
|
||||
// console.log(form2AppWorkflow(appForm, t));
|
||||
setWorkflowData({ nodes, edges });
|
||||
}, [appForm, setWorkflowData, allDatasets, t]);
|
||||
}, [appForm, setWorkflowData, t]);
|
||||
|
||||
useEffect(() => {
|
||||
setRenderEdit(!quoteData);
|
||||
}, [quoteData, setRenderEdit]);
|
||||
|
||||
const { ChatContainer, restartChat, loading } = useChatTest({
|
||||
...workflowData,
|
||||
@@ -44,41 +49,56 @@ const ChatTest = ({ appForm }: Props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
display={'flex'}
|
||||
position={'relative'}
|
||||
flexDirection={'column'}
|
||||
h={'100%'}
|
||||
py={4}
|
||||
>
|
||||
<Flex px={[2, 5]}>
|
||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'} flex={1} color={'myGray.900'}>
|
||||
{appT('chat_debug')}
|
||||
<Flex h={'full'} gap={2}>
|
||||
<MyBox
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
isLoading={loading}
|
||||
display={'flex'}
|
||||
position={'relative'}
|
||||
flexDirection={'column'}
|
||||
h={'full'}
|
||||
py={4}
|
||||
{...cardStyles}
|
||||
boxShadow={'3'}
|
||||
>
|
||||
<Flex px={[2, 5]}>
|
||||
<Box fontSize={['md', 'lg']} fontWeight={'bold'} flex={1} color={'myGray.900'}>
|
||||
{t('app:chat_debug')}
|
||||
</Box>
|
||||
<MyTooltip label={t('common:core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||
variant={'whiteDanger'}
|
||||
borderRadius={'md'}
|
||||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
restartChat();
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
<MyTooltip label={t('common:core.chat.Restart')}>
|
||||
<IconButton
|
||||
className="chat"
|
||||
size={'smSquare'}
|
||||
icon={<MyIcon name={'common/clearLight'} w={'14px'} />}
|
||||
variant={'whiteDanger'}
|
||||
borderRadius={'md'}
|
||||
aria-label={'delete'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
restartChat();
|
||||
}}
|
||||
</MyBox>
|
||||
{quoteData && (
|
||||
<Box flex={'1 0 0'} w={0} maxW={'560px'} {...cardStyles} boxShadow={'3'}>
|
||||
<ChatQuoteList
|
||||
rawSearch={quoteData.rawSearch}
|
||||
metadata={quoteData.metadata}
|
||||
onClose={() => setQuoteData(undefined)}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
<Box flex={1}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
</MyBox>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = ({ appForm }: Props) => {
|
||||
const Render = ({ appForm, setRenderEdit }: Props) => {
|
||||
const { chatId } = useChatStore();
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
@@ -95,10 +115,11 @@ const Render = ({ appForm }: Props) => {
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest appForm={appForm} />
|
||||
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
|
||||
</ChatRecordContextProvider>
|
||||
</ChatItemContextProvider>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
import ChatTest from './ChatTest';
|
||||
@@ -21,6 +21,7 @@ const Edit = ({
|
||||
setPast: (value: React.SetStateAction<SimpleAppSnapshotType[]>) => void;
|
||||
}) => {
|
||||
const { isPc } = useSystem();
|
||||
const [renderEdit, setRenderEdit] = useState(true);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -32,24 +33,26 @@ const Edit = ({
|
||||
borderRadius={'lg'}
|
||||
overflowY={['auto', 'unset']}
|
||||
>
|
||||
<Box
|
||||
className={styles.EditAppBox}
|
||||
pr={[0, 1]}
|
||||
overflowY={'auto'}
|
||||
minW={['auto', '580px']}
|
||||
flex={'1'}
|
||||
>
|
||||
<Box {...cardStyles} boxShadow={'2'}>
|
||||
<AppCard appForm={appForm} setPast={setPast} />
|
||||
</Box>
|
||||
{renderEdit && (
|
||||
<Box
|
||||
className={styles.EditAppBox}
|
||||
pr={[0, 1]}
|
||||
overflowY={'auto'}
|
||||
minW={['auto', '580px']}
|
||||
flex={'1'}
|
||||
>
|
||||
<Box {...cardStyles} boxShadow={'2'}>
|
||||
<AppCard appForm={appForm} setPast={setPast} />
|
||||
</Box>
|
||||
|
||||
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
|
||||
<EditForm appForm={appForm} setAppForm={setAppForm} />
|
||||
<Box mt={4} {...cardStyles} boxShadow={'3.5'}>
|
||||
<EditForm appForm={appForm} setAppForm={setAppForm} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{isPc && (
|
||||
<Box {...cardStyles} boxShadow={'3'} flex={'2 0 0'} w={0} mb={3}>
|
||||
<ChatTest appForm={appForm} />
|
||||
<Box flex={'2 0 0'} w={0} mb={3}>
|
||||
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
@@ -68,18 +67,9 @@ const EditForm = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
|
||||
const { allDatasets } = useDatasetStore();
|
||||
const selectDatasets = useMemo(() => appForm?.dataset?.datasets, [appForm]);
|
||||
const [, startTst] = useTransition();
|
||||
|
||||
const selectDatasets = useMemo(
|
||||
() =>
|
||||
allDatasets.filter((item) =>
|
||||
appForm.dataset?.datasets.find((dataset) => dataset.datasetId === item._id)
|
||||
),
|
||||
[allDatasets, appForm?.dataset?.datasets]
|
||||
);
|
||||
|
||||
const {
|
||||
isOpen: isOpenDatasetSelect,
|
||||
onOpen: onOpenKbSelect,
|
||||
@@ -115,6 +105,7 @@ const EditForm = ({
|
||||
const tokenLimit = useMemo(() => {
|
||||
return selectedModel?.quoteMaxToken || 3000;
|
||||
}, [selectedModel?.quoteMaxToken]);
|
||||
|
||||
// Force close image select when model not support vision
|
||||
useEffect(() => {
|
||||
if (!selectedModel.vision) {
|
||||
@@ -252,7 +243,7 @@ const EditForm = ({
|
||||
)}
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={[2, 4]}>
|
||||
{selectDatasets.map((item) => (
|
||||
<MyTooltip key={item._id} label={t('common:core.dataset.Read Dataset')}>
|
||||
<MyTooltip key={item.datasetId} label={t('common:core.dataset.Read Dataset')}>
|
||||
<Flex
|
||||
overflow={'hidden'}
|
||||
alignItems={'center'}
|
||||
@@ -266,7 +257,7 @@ const EditForm = ({
|
||||
router.push({
|
||||
pathname: '/dataset/detail',
|
||||
query: {
|
||||
datasetId: item._id
|
||||
datasetId: item.datasetId
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -413,8 +404,10 @@ const EditForm = ({
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenDatasetSelect}
|
||||
defaultSelectedDatasets={selectDatasets.map((item) => ({
|
||||
datasetId: item._id,
|
||||
vectorModel: item.vectorModel
|
||||
datasetId: item.datasetId,
|
||||
vectorModel: item.vectorModel,
|
||||
name: item.name,
|
||||
avatar: item.avatar
|
||||
}))}
|
||||
onClose={onCloseKbSelect}
|
||||
onChange={(e) => {
|
||||
@@ -441,8 +434,6 @@ const EditForm = ({
|
||||
...e
|
||||
}
|
||||
}));
|
||||
|
||||
console.dir(e);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { publishStatusStyle } from '../constants';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import SaveButton from '../Workflow/components/SaveButton';
|
||||
import { useBoolean, useDebounceEffect, useLockFn } from 'ahooks';
|
||||
import { appWorkflow2Form } from '@fastgpt/global/core/app/utils';
|
||||
@@ -61,7 +60,6 @@ const Header = ({
|
||||
const currentTab = useContextSelector(AppContext, (v) => v.currentTab);
|
||||
|
||||
const { lastAppListRouteType } = useSystemStore();
|
||||
const { allDatasets } = useDatasetStore();
|
||||
|
||||
const { data: paths = [] } = useRequest2(() => getAppFolderPath(appId), {
|
||||
manual: false,
|
||||
@@ -159,7 +157,7 @@ const Header = ({
|
||||
const val = compareSimpleAppSnapshot(savedSnapshot?.appForm, appForm);
|
||||
setIsSaved(val);
|
||||
},
|
||||
[past, allDatasets],
|
||||
[past],
|
||||
{ wait: 500 }
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { SimpleAppSnapshotType, useSimpleAppSnapshots } from './useSnapshots';
|
||||
import { useDebounceEffect, useMount } from 'ahooks';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
||||
import { getAppConfigByDiff } from '@/web/core/app/diff';
|
||||
|
||||
@@ -19,7 +18,6 @@ const PublishChannel = dynamic(() => import('../Publish'));
|
||||
|
||||
const SimpleEdit = () => {
|
||||
const { t } = useTranslation();
|
||||
const { loadAllDatasets } = useDatasetStore();
|
||||
|
||||
const { currentTab, appDetail } = useContextSelector(AppContext, (v) => v);
|
||||
const { forbiddenSaveSnapshot, past, setPast, saveSnapshot } = useSimpleAppSnapshots(
|
||||
@@ -30,9 +28,6 @@ const SimpleEdit = () => {
|
||||
|
||||
// Init app form
|
||||
useMount(() => {
|
||||
// show selected dataset
|
||||
loadAllDatasets();
|
||||
|
||||
if (appDetail.version !== 'v2') {
|
||||
return setAppForm(
|
||||
appWorkflow2Form({
|
||||
|
||||
@@ -20,6 +20,7 @@ import ChatRecordContextProvider, {
|
||||
} from '@/web/core/chat/context/chatRecordContext';
|
||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
@@ -41,10 +42,13 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
});
|
||||
const pluginRunTab = useContextSelector(ChatItemContext, (v) => v.pluginRunTab);
|
||||
const setPluginRunTab = useContextSelector(ChatItemContext, (v) => v.setPluginRunTab);
|
||||
const quoteData = useContextSelector(ChatItemContext, (v) => v.quoteData);
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex h={'full'}>
|
||||
<Box
|
||||
zIndex={300}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
@@ -53,7 +57,10 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
onClick={onClose}
|
||||
onClick={() => {
|
||||
setQuoteData(undefined);
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
<MyBox
|
||||
isLoading={loading}
|
||||
@@ -64,7 +71,7 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
top={5}
|
||||
right={0}
|
||||
h={isOpen ? '95%' : '0'}
|
||||
w={isOpen ? ['100%', '460px'] : '0'}
|
||||
w={isOpen ? (quoteData ? ['100%', '960px'] : ['100%', '460px']) : '0'}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'md'}
|
||||
@@ -137,11 +144,34 @@ const ChatTest = ({ isOpen, nodes = [], edges = [], onClose }: Props) => {
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
<Box flex={'1 0 0'} overflow={'auto'}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
<Flex flex={'1 0 0'} alignItems={'end'}>
|
||||
<Box flex={'1 0 0'} h={'100%'} overflow={'auto'}>
|
||||
<ChatContainer />
|
||||
</Box>
|
||||
|
||||
{quoteData && (
|
||||
<Box
|
||||
flex={'1 0 0'}
|
||||
w={0}
|
||||
mr={4}
|
||||
maxW={'440px'}
|
||||
h={'98%'}
|
||||
bg={'white'}
|
||||
boxShadow={
|
||||
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
|
||||
}
|
||||
borderRadius={'md'}
|
||||
>
|
||||
<ChatQuoteList
|
||||
rawSearch={quoteData.rawSearch}
|
||||
metadata={quoteData.metadata}
|
||||
onClose={() => setQuoteData(undefined)}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -162,6 +192,7 @@ const Render = (Props: Props) => {
|
||||
showRouteToAppDetail={true}
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
|
||||
@@ -84,7 +84,7 @@ const ExtractFieldModal = ({
|
||||
<MySelect<string>
|
||||
list={toolValueTypeList}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('valueType', e as any);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -217,7 +217,7 @@ const RenderHttpMethodAndUrl = React.memo(function RenderHttpMethodAndUrl({
|
||||
value: 'PATCH'
|
||||
}
|
||||
]}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
|
||||
@@ -396,7 +396,7 @@ const ConditionSelect = ({
|
||||
w={'100%'}
|
||||
list={filterQuiredConditionList}
|
||||
value={condition}
|
||||
onchange={onSelect}
|
||||
onChange={onSelect}
|
||||
placeholder={t('common:chose_condition')}
|
||||
/>
|
||||
);
|
||||
@@ -441,7 +441,7 @@ const ConditionValueInput = ({
|
||||
{ label: 'True', value: 'true' },
|
||||
{ label: 'False', value: 'false' }
|
||||
]}
|
||||
onchange={onChange}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
placeholder={workflowT('ifelse.Select value')}
|
||||
isDisabled={
|
||||
|
||||
@@ -235,7 +235,7 @@ const NodeLaf = (props: NodeProps<FlowNodeItemType>) => {
|
||||
isLoading={isLoadingFunctions}
|
||||
list={lafFunctionSelectList}
|
||||
placeholder={t('common:core.module.laf.Select laf function')}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
|
||||
@@ -209,7 +209,7 @@ const InputTypeConfig = ({
|
||||
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
|
||||
)}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
@@ -346,7 +346,7 @@ const InputTypeConfig = ({
|
||||
? defaultValue
|
||||
: ''
|
||||
}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('defaultValue', e);
|
||||
}}
|
||||
w={'200px'}
|
||||
|
||||
@@ -144,7 +144,7 @@ const PluginOutputEditModal = ({
|
||||
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
|
||||
)}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -123,7 +123,7 @@ const ToolParamsEditModal = ({
|
||||
<MySelect
|
||||
list={toolValueTypeList}
|
||||
value={valueType}
|
||||
onchange={(e: any) => {
|
||||
onChange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -137,7 +137,7 @@ const FieldModal = ({
|
||||
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
|
||||
)}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ const SelectRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={item.list || []}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import type { RenderInputProps } from '../type';
|
||||
import { Box, Button, Flex, Grid, Switch, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api';
|
||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
@@ -32,26 +29,17 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
usingReRank: false
|
||||
});
|
||||
|
||||
const { allDatasets, loadAllDatasets } = useDatasetStore();
|
||||
const {
|
||||
isOpen: isOpenDatasetSelect,
|
||||
onOpen: onOpenDatasetSelect,
|
||||
onClose: onCloseDatasetSelect
|
||||
} = useDisclosure();
|
||||
|
||||
const selectedDatasetsValue = useMemo(() => {
|
||||
const selectedDatasets = useMemo(() => {
|
||||
if (Array.isArray(item.value)) return item.value as SelectedDatasetType;
|
||||
return [] as SelectedDatasetType;
|
||||
}, [item.value]);
|
||||
|
||||
const selectedDatasets = useMemo(() => {
|
||||
return allDatasets.filter((dataset) =>
|
||||
selectedDatasetsValue?.find((item) => item.datasetId === dataset._id)
|
||||
);
|
||||
}, [allDatasets, selectedDatasetsValue]);
|
||||
|
||||
useQuery(['loadAllDatasets'], loadAllDatasets);
|
||||
|
||||
useEffect(() => {
|
||||
inputs.forEach((input) => {
|
||||
// @ts-ignore
|
||||
@@ -82,7 +70,7 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
</Button>
|
||||
{selectedDatasets.map((item) => (
|
||||
<Flex
|
||||
key={item._id}
|
||||
key={item.datasetId}
|
||||
alignItems={'center'}
|
||||
h={10}
|
||||
boxShadow={'sm'}
|
||||
@@ -108,7 +96,12 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
{isOpenDatasetSelect && (
|
||||
<DatasetSelectModal
|
||||
isOpen={isOpenDatasetSelect}
|
||||
defaultSelectedDatasets={selectedDatasetsValue}
|
||||
defaultSelectedDatasets={selectedDatasets.map((item) => ({
|
||||
datasetId: item.datasetId,
|
||||
vectorModel: item.vectorModel,
|
||||
name: item.name,
|
||||
avatar: item.avatar
|
||||
}))}
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -133,7 +126,6 @@ export const SelectDatasetRender = React.memo(function SelectDatasetRender({
|
||||
onCloseDatasetSelect,
|
||||
onOpenDatasetSelect,
|
||||
selectedDatasets,
|
||||
selectedDatasetsValue,
|
||||
t
|
||||
]);
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import DatasetParamsModal, { DatasetParamsProps } from '@/components/core/app/DatasetParamsModal';
|
||||
import DatasetParamsModal from '@/components/core/app/DatasetParamsModal';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import SearchParamsTip from '@/components/core/dataset/SearchParamsTip';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowContext } from '@/pageComponents/app/detail/WorkflowComponents/context';
|
||||
import { getWebLLMModel } from '@/web/common/system/utils';
|
||||
import { defaultDatasetMaxTokens } from '@fastgpt/global/core/app/constants';
|
||||
import { AppDatasetSearchParamsType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
@@ -21,11 +22,14 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { defaultModels } = useSystemStore();
|
||||
|
||||
const [data, setData] = useState<DatasetParamsProps>({
|
||||
const [data, setData] = useState<AppDatasetSearchParamsType>({
|
||||
searchMode: DatasetSearchModeEnum.embedding,
|
||||
limit: 5,
|
||||
embeddingWeight: 0.5,
|
||||
limit: 3000,
|
||||
similarity: 0.5,
|
||||
usingReRank: false,
|
||||
rerankModel: defaultModels.llm?.model,
|
||||
rerankWeight: 0.6,
|
||||
datasetSearchUsingExtensionQuery: true,
|
||||
datasetSearchExtensionModel: defaultModels.llm?.model,
|
||||
datasetSearchExtensionBg: ''
|
||||
|
||||
@@ -57,7 +57,7 @@ const SelectAiModelRender = ({ item, nodeId }: RenderInputProps) => {
|
||||
value: item.model,
|
||||
label: item.name
|
||||
}))}
|
||||
onchange={onChangeModel}
|
||||
onChange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
}, [item.value, modelList, onChangeModel]);
|
||||
|
||||
@@ -201,7 +201,7 @@ const EditModal = ({ onClose, ...props }: RenderInputProps & { onClose: () => vo
|
||||
description: t('workflow:dataset_quote_role_user_option_desc')
|
||||
}
|
||||
]}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('quoteRole', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -131,7 +131,7 @@ const FieldModal = ({
|
||||
(item) => item.value !== WorkflowIOValueTypeEnum.arrayAny
|
||||
)}
|
||||
value={valueType}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -97,7 +97,7 @@ const EditFieldModal = ({
|
||||
<MySelect
|
||||
list={toolValueTypeList}
|
||||
value={valueType}
|
||||
onchange={(e: any) => {
|
||||
onChange={(e: any) => {
|
||||
setValue('valueType', e);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -289,7 +289,7 @@ const TemplateMarketModal = ({
|
||||
<MySelect<TemplateAppType>
|
||||
h={'8'}
|
||||
value={currentAppType}
|
||||
onchange={(value) => {
|
||||
onChange={(value) => {
|
||||
setCurrentAppType(value);
|
||||
}}
|
||||
bg={'myGray.100'}
|
||||
|
||||
@@ -3,31 +3,32 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const AppTypeTag = ({ type }: { type: AppTypeEnum }) => {
|
||||
const { appT } = useI18n();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const map = useRef({
|
||||
[AppTypeEnum.simple]: {
|
||||
label: appT('type.Simple bot'),
|
||||
label: t('app:type.Simple bot'),
|
||||
icon: 'core/app/type/simple',
|
||||
bg: '#DBF3FF',
|
||||
color: '#0884DD'
|
||||
},
|
||||
[AppTypeEnum.workflow]: {
|
||||
label: appT('type.Workflow bot'),
|
||||
label: t('app:type.Workflow bot'),
|
||||
icon: 'core/app/type/workflow',
|
||||
bg: '#E4E1FC',
|
||||
color: '#6F5DD7'
|
||||
},
|
||||
[AppTypeEnum.plugin]: {
|
||||
label: appT('type.Plugin'),
|
||||
label: t('app:type.Plugin'),
|
||||
icon: 'core/app/type/plugin',
|
||||
bg: '#D0F5EE',
|
||||
color: '#007E7C'
|
||||
},
|
||||
[AppTypeEnum.httpPlugin]: {
|
||||
label: appT('type.Http plugin'),
|
||||
label: t('app:type.Http plugin'),
|
||||
icon: 'core/app/type/httpPlugin',
|
||||
bg: '#FFE4EE',
|
||||
color: '#E82F72'
|
||||
|
||||
@@ -46,6 +46,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name);
|
||||
const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar);
|
||||
const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail);
|
||||
const setQuoteData = useContextSelector(ChatItemContext, (v) => v.setQuoteData);
|
||||
|
||||
const concatHistory = useMemo(() => {
|
||||
const formatHistories: HistoryItemType[] = histories.map((item) => {
|
||||
@@ -144,7 +145,10 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
borderRadius={'xl'}
|
||||
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
|
||||
overflow={'hidden'}
|
||||
onClick={() => onChangeChatId()}
|
||||
onClick={() => {
|
||||
onChangeChatId();
|
||||
setQuoteData(undefined);
|
||||
}}
|
||||
>
|
||||
{t('common:core.chat.New Chat')}
|
||||
</Button>
|
||||
@@ -199,6 +203,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
: {
|
||||
onClick: () => {
|
||||
onChangeChatId(item.id);
|
||||
setQuoteData(undefined);
|
||||
}
|
||||
})}
|
||||
{...(i !== concatHistory.length - 1 && {
|
||||
@@ -270,6 +275,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) =
|
||||
onDelHistory(item.id);
|
||||
if (item.id === activeChatId) {
|
||||
onChangeChatId();
|
||||
setQuoteData(undefined);
|
||||
}
|
||||
},
|
||||
type: 'danger'
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import Markdown from '@/components/Markdown';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { Dispatch, MutableRefObject, SetStateAction, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import InputDataModal from '@/pageComponents/dataset/detail/InputDataModal';
|
||||
|
||||
const CollectionQuoteItem = ({
|
||||
quoteRefs,
|
||||
quoteIndex,
|
||||
setQuoteIndex,
|
||||
refreshList,
|
||||
canEdit,
|
||||
|
||||
updated,
|
||||
isCurrentSelected,
|
||||
q,
|
||||
a,
|
||||
dataId,
|
||||
collectionId
|
||||
}: {
|
||||
quoteRefs: MutableRefObject<Map<string, HTMLDivElement | null>>;
|
||||
quoteIndex: number;
|
||||
setQuoteIndex: Dispatch<SetStateAction<number>>;
|
||||
refreshList: () => void;
|
||||
canEdit: boolean;
|
||||
|
||||
updated?: boolean;
|
||||
isCurrentSelected: boolean;
|
||||
q: string;
|
||||
a?: string;
|
||||
dataId: string;
|
||||
collectionId: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const hasBeenSearched = quoteIndex !== undefined && quoteIndex > -1;
|
||||
const [editInputData, setEditInputData] = useState<{ dataId: string; collectionId: string }>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
ref={(el: HTMLDivElement | null) => {
|
||||
quoteRefs.current.set(dataId, el);
|
||||
}}
|
||||
p={2}
|
||||
py={2}
|
||||
cursor={hasBeenSearched ? 'pointer' : 'default'}
|
||||
bg={isCurrentSelected ? '#FFF9E7' : hasBeenSearched ? '#FFFCF2' : ''}
|
||||
position={'relative'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid '}
|
||||
borderColor={isCurrentSelected ? 'yellow.200' : 'transparent'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
_hover={
|
||||
hasBeenSearched
|
||||
? {
|
||||
'& .hover-data': { visibility: 'visible' }
|
||||
}
|
||||
: {
|
||||
bg: 'linear-gradient(180deg, #FBFBFC 7.61%, #F0F1F6 100%)',
|
||||
borderTopColor: 'myGray.50',
|
||||
'& .hover-data': { visibility: 'visible' }
|
||||
}
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (hasBeenSearched) {
|
||||
setQuoteIndex(quoteIndex);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{updated && (
|
||||
<Flex mt={2}>
|
||||
<Box
|
||||
bg={'green.50'}
|
||||
border={'1px solid'}
|
||||
borderRadius={'xs'}
|
||||
borderColor={'green.100'}
|
||||
px={1}
|
||||
color={'green.600'}
|
||||
>
|
||||
{t('common:core.dataset.data.Updated')}
|
||||
</Box>
|
||||
<Box flex={1} borderBottom={'1px dashed'} borderColor={'green.200'} />
|
||||
</Flex>
|
||||
)}
|
||||
<Markdown source={q} />
|
||||
{!!a && (
|
||||
<Box>
|
||||
<Markdown source={a} />
|
||||
</Box>
|
||||
)}
|
||||
<Flex
|
||||
className="hover-data"
|
||||
position={'absolute'}
|
||||
bottom={2}
|
||||
right={5}
|
||||
gap={1.5}
|
||||
visibility={'hidden'}
|
||||
>
|
||||
<MyTooltip label={t('common:core.dataset.Quote Length')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'10px'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'white'}
|
||||
rounded={'sm'}
|
||||
px={2}
|
||||
py={1}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
>
|
||||
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
|
||||
{q.length + (a?.length || 0)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
{canEdit && (
|
||||
<MyTooltip label={t('common:core.dataset.data.Edit')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'10px'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'white'}
|
||||
rounded={'sm'}
|
||||
px={1}
|
||||
py={1}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
cursor={'pointer'}
|
||||
onClick={() =>
|
||||
setEditInputData({
|
||||
dataId,
|
||||
collectionId
|
||||
})
|
||||
}
|
||||
>
|
||||
<MyIcon name="common/edit" w={'14px'} color={'myGray.500'} />
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={t('common:common.Copy')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'10px'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'white'}
|
||||
rounded={'sm'}
|
||||
px={1}
|
||||
py={1}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
cursor={'pointer'}
|
||||
onClick={() => copyData(`${q}${a ? '\n' + a : ''}`)}
|
||||
>
|
||||
<MyIcon name="copy" w={'14px'} color={'myGray.500'} />
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
</Box>
|
||||
{editInputData && (
|
||||
<InputDataModal
|
||||
onClose={() => setEditInputData(undefined)}
|
||||
onSuccess={refreshList}
|
||||
dataId={editInputData.dataId}
|
||||
collectionId={editInputData.collectionId}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionQuoteItem;
|
||||
@@ -0,0 +1,304 @@
|
||||
import { Box, Flex, HStack } from '@chakra-ui/react';
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import DownloadButton from './DownloadButton';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { downloadFetch } from '@/web/common/system/utils';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { getDatasetDataPermission } from '@/web/core/dataset/api';
|
||||
import ScoreTag from './ScoreTag';
|
||||
import { formatScore } from '@/components/core/dataset/QuoteItem';
|
||||
import NavButton from './NavButton';
|
||||
import { useLinkedScroll } from '@fastgpt/web/hooks/useLinkedScroll';
|
||||
import CollectionQuoteItem from './CollectionQuoteItem';
|
||||
import { GetCollectionQuoteDataProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { getCollectionQuote } from '@/web/core/chat/api';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource';
|
||||
import { QuoteDataItemType } from '@/service/core/chat/constants';
|
||||
|
||||
const CollectionReader = ({
|
||||
rawSearch,
|
||||
metadata,
|
||||
onClose
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
metadata: GetCollectionQuoteDataProps;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const { collectionId, datasetId, chatItemDataId, sourceId, sourceName } = metadata;
|
||||
const [quoteIndex, setQuoteIndex] = useState(0);
|
||||
|
||||
// Get dataset permission
|
||||
const { data: datasetData } = useRequest2(async () => await getDatasetDataPermission(datasetId), {
|
||||
manual: !userInfo || !datasetId,
|
||||
refreshDeps: [datasetId, userInfo]
|
||||
});
|
||||
|
||||
const filterResults = useMemo(() => {
|
||||
setQuoteIndex(0);
|
||||
return rawSearch
|
||||
.filter((item) => item.collectionId === collectionId)
|
||||
.sort((a, b) => (a.chunkIndex || 0) - (b.chunkIndex || 0));
|
||||
}, [collectionId, rawSearch]);
|
||||
const currentQuoteItem = useMemo(() => {
|
||||
const item = filterResults[quoteIndex];
|
||||
if (item) {
|
||||
return {
|
||||
id: item.id,
|
||||
index: item.chunkIndex,
|
||||
score: item.score
|
||||
};
|
||||
}
|
||||
}, [filterResults, quoteIndex]);
|
||||
|
||||
// Get quote list
|
||||
const params = useMemo(
|
||||
() => ({
|
||||
collectionId,
|
||||
chatItemDataId,
|
||||
chatId: metadata.chatId,
|
||||
appId: metadata.appId,
|
||||
...metadata.outLinkAuthData
|
||||
}),
|
||||
[chatItemDataId, collectionId, metadata.appId, metadata.chatId, metadata.outLinkAuthData]
|
||||
);
|
||||
|
||||
const {
|
||||
dataList: datasetDataList,
|
||||
isLoading,
|
||||
ScrollData,
|
||||
itemRefs,
|
||||
loadInitData
|
||||
} = useLinkedScroll(getCollectionQuote, {
|
||||
params,
|
||||
currentData: currentQuoteItem
|
||||
});
|
||||
|
||||
const isDeleted = useMemo(
|
||||
() => !isLoading && !datasetDataList.find((item) => item._id === currentQuoteItem?.id),
|
||||
[datasetDataList, currentQuoteItem?.id, isLoading]
|
||||
);
|
||||
|
||||
const formatedDataList = useMemo(
|
||||
() =>
|
||||
datasetDataList.map((item: QuoteDataItemType) => {
|
||||
const isCurrentSelected = currentQuoteItem?.id === item._id;
|
||||
const quoteIndex = filterResults.findIndex((res) => res.id === item._id);
|
||||
|
||||
return {
|
||||
...item,
|
||||
isCurrentSelected,
|
||||
quoteIndex
|
||||
};
|
||||
}),
|
||||
[currentQuoteItem?.id, datasetDataList, filterResults]
|
||||
);
|
||||
|
||||
const { runAsync: handleDownload } = useRequest2(async () => {
|
||||
await downloadFetch({
|
||||
url: '/api/core/dataset/collection/export',
|
||||
filename: 'data.csv',
|
||||
body: {
|
||||
appId: metadata.appId,
|
||||
chatId: metadata.chatId,
|
||||
chatItemDataId,
|
||||
collectionId,
|
||||
...metadata.outLinkAuthData
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const handleRead = getCollectionSourceAndOpen({
|
||||
appId: metadata.appId,
|
||||
chatId: metadata.chatId,
|
||||
chatItemDataId,
|
||||
collectionId,
|
||||
...metadata.outLinkAuthData
|
||||
});
|
||||
|
||||
return (
|
||||
<MyBox display={'flex'} flexDirection={'column'} h={'full'}>
|
||||
{/* title */}
|
||||
<Box borderBottom={'1px solid'} borderBottomColor={'myGray.150'} px={3} py={2}>
|
||||
{/* name */}
|
||||
<HStack>
|
||||
<Flex alignItems={'center'} flex={'1 0 0'} w={0}>
|
||||
<MyIcon
|
||||
name={getSourceNameIcon({ sourceId, sourceName }) as any}
|
||||
w={['1rem', '1.25rem']}
|
||||
color={'primary.600'}
|
||||
/>
|
||||
<Box
|
||||
ml={1}
|
||||
maxW={['200px', '220px']}
|
||||
className={'textEllipsis'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
fontWeight={'medium'}
|
||||
{...(!!userInfo &&
|
||||
datasetData?.permission?.hasReadPer && {
|
||||
cursor: 'pointer',
|
||||
_hover: { color: 'primary.600', textDecoration: 'underline' },
|
||||
onClick: () => {
|
||||
router.push(
|
||||
`/dataset/detail?datasetId=${datasetId}¤tTab=dataCard&collectionId=${collectionId}`
|
||||
);
|
||||
}
|
||||
})}
|
||||
>
|
||||
{sourceName || t('common:common.UnKnow Source')}
|
||||
</Box>
|
||||
<Box ml={3}>
|
||||
<DownloadButton
|
||||
canAccessRawData={true}
|
||||
onDownload={handleDownload}
|
||||
onRead={handleRead}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<MyIconButton
|
||||
icon={'common/closeLight'}
|
||||
size={'1.25rem'}
|
||||
color={'myGray.900'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</HStack>
|
||||
{datasetData?.permission?.hasReadPer && (
|
||||
<Box
|
||||
fontSize={'mini'}
|
||||
color={'myGray.500'}
|
||||
{...(!!userInfo
|
||||
? {
|
||||
cursor: 'pointer',
|
||||
_hover: { color: 'primary.600', textDecoration: 'underline' },
|
||||
onClick: () => {
|
||||
router.push(`/dataset/detail?datasetId=${datasetId}`);
|
||||
}
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
{t('chat:data_source', {
|
||||
name: datasetData.datasetName
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* header control */}
|
||||
{datasetDataList.length > 0 && (
|
||||
<Box>
|
||||
<Flex
|
||||
w={'full'}
|
||||
px={4}
|
||||
py={2}
|
||||
alignItems={'center'}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.150'}
|
||||
>
|
||||
{/* 引用序号 */}
|
||||
<Flex fontSize={'mini'} mr={3} alignItems={'center'} gap={1}>
|
||||
<Box as={'span'} color={'myGray.900'}>
|
||||
{t('common:core.chat.Quote')} {quoteIndex + 1}
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
/
|
||||
</Box>
|
||||
<Box as={'span'} color={'myGray.500'}>
|
||||
{filterResults.length}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* 检索分数 */}
|
||||
{currentQuoteItem?.score ? (
|
||||
<ScoreTag {...formatScore(currentQuoteItem?.score)} />
|
||||
) : isDeleted ? (
|
||||
<Flex
|
||||
borderRadius={'sm'}
|
||||
py={1}
|
||||
px={2}
|
||||
color={'red.600'}
|
||||
bg={'red.50'}
|
||||
alignItems={'center'}
|
||||
fontSize={'11px'}
|
||||
>
|
||||
<MyIcon name="common/info" w={'14px'} mr={1} color={'red.600'} />
|
||||
{t('chat:chat.quote.deleted')}
|
||||
</Flex>
|
||||
) : null}
|
||||
|
||||
<Box flex={1} />
|
||||
|
||||
{/* 检索按钮 */}
|
||||
<Flex gap={1}>
|
||||
<NavButton
|
||||
direction="up"
|
||||
isDisabled={quoteIndex === 0}
|
||||
onClick={() => setQuoteIndex(quoteIndex - 1)}
|
||||
/>
|
||||
<NavButton
|
||||
direction="down"
|
||||
isDisabled={quoteIndex === filterResults.length - 1}
|
||||
onClick={() => setQuoteIndex(quoteIndex + 1)}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box fontSize={'mini'} color={'myGray.500'} bg={'myGray.25'} px={4} py={1}>
|
||||
{t('common:core.chat.quote.Quote Tip')}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* quote list */}
|
||||
{isLoading || datasetDataList.length > 0 ? (
|
||||
<ScrollData flex={'1 0 0'} mt={2} px={5} py={1}>
|
||||
<Flex flexDir={'column'}>
|
||||
{formatedDataList.map((item, index) => (
|
||||
<CollectionQuoteItem
|
||||
key={item._id}
|
||||
quoteRefs={itemRefs as React.MutableRefObject<Map<string, HTMLDivElement | null>>}
|
||||
quoteIndex={item.quoteIndex}
|
||||
setQuoteIndex={setQuoteIndex}
|
||||
refreshList={() => loadInitData({ scrollWhenFinish: false, refresh: true })}
|
||||
updated={item.updated}
|
||||
isCurrentSelected={item.isCurrentSelected}
|
||||
q={item.q}
|
||||
a={item.a}
|
||||
dataId={item._id}
|
||||
collectionId={collectionId}
|
||||
canEdit={!!userInfo && !!datasetData?.permission?.hasWritePer}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
</ScrollData>
|
||||
) : (
|
||||
<Flex
|
||||
flex={'1 0 0'}
|
||||
flexDirection={'column'}
|
||||
gap={1}
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<Box border={'1px dashed'} borderColor={'myGray.400'} p={2} borderRadius={'full'}>
|
||||
<MyIcon name="common/fileNotFound" />
|
||||
</Box>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
{t('chat:chat.quote.No Data')}
|
||||
</Box>
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionReader;
|
||||
@@ -0,0 +1,54 @@
|
||||
import MyMenu from '@fastgpt/web/components/common/MyMenu';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
const DownloadButton = ({
|
||||
canAccessRawData,
|
||||
onDownload,
|
||||
onRead
|
||||
}: {
|
||||
canAccessRawData: boolean;
|
||||
onDownload: () => void;
|
||||
onRead: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (canAccessRawData) {
|
||||
return (
|
||||
<MyMenu
|
||||
size={'xs'}
|
||||
Button={
|
||||
<MyIconButton
|
||||
icon="common/download"
|
||||
size={'1rem'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.250'}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
/>
|
||||
}
|
||||
menuList={[
|
||||
{
|
||||
children: [
|
||||
{
|
||||
label: t('chat:download_chunks'),
|
||||
type: 'grayBg',
|
||||
onClick: onDownload
|
||||
},
|
||||
{
|
||||
label: t('chat:read_raw_source'),
|
||||
type: 'grayBg',
|
||||
onClick: onRead
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <MyIconButton icon="common/download" size={'1rem'} onClick={onDownload} />;
|
||||
};
|
||||
|
||||
export default DownloadButton;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
||||
const NavButton = ({
|
||||
direction,
|
||||
isDisabled,
|
||||
onClick
|
||||
}: {
|
||||
direction: 'up' | 'down';
|
||||
isDisabled: boolean;
|
||||
onClick: () => void;
|
||||
}) => {
|
||||
const isUp = direction === 'up';
|
||||
|
||||
const baseStyles = {
|
||||
color: 'myGray.500',
|
||||
border: '1px solid',
|
||||
borderColor: 'myGray.150',
|
||||
borderRadius: 'sm',
|
||||
w: 6,
|
||||
h: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'all 0.2s'
|
||||
};
|
||||
|
||||
const stateStyles = isDisabled
|
||||
? {
|
||||
cursor: 'not-allowed',
|
||||
opacity: 0.5,
|
||||
_hover: {}
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
opacity: 1,
|
||||
_hover: { bg: 'myGray.100' },
|
||||
onClick
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex {...baseStyles} {...stateStyles}>
|
||||
<MyIcon name={isUp ? `common/solidChevronUp` : `common/solidChevronDown`} w={'18px'} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavButton;
|
||||
163
projects/app/src/pageComponents/chat/ChatQuoteList/QuoteItem.tsx
Normal file
163
projects/app/src/pageComponents/chat/ChatQuoteList/QuoteItem.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { ScoreItemType } from '@/components/core/dataset/QuoteItem';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import ScoreTag from './ScoreTag';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
|
||||
const QuoteItem = ({
|
||||
index,
|
||||
icon,
|
||||
sourceName,
|
||||
score,
|
||||
q,
|
||||
a
|
||||
}: {
|
||||
index: number;
|
||||
icon: string;
|
||||
sourceName: string;
|
||||
score: { primaryScore?: ScoreItemType; secondaryScore: ScoreItemType[] };
|
||||
q: string;
|
||||
a?: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
const isDeleted = !q;
|
||||
|
||||
return (
|
||||
<Box
|
||||
p={2}
|
||||
position={'relative'}
|
||||
overflow={'hidden'}
|
||||
border={'1px solid transparent'}
|
||||
borderBottomColor={'myGray.150'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
_hover={{
|
||||
bg: 'linear-gradient(180deg, #FBFBFC 7.61%, #F0F1F6 100%)',
|
||||
borderTopColor: 'myGray.50',
|
||||
'& .hover-data': { visibility: 'visible' }
|
||||
}}
|
||||
>
|
||||
<Flex gap={2} alignItems={'center'} mb={2}>
|
||||
<Box
|
||||
alignItems={'center'}
|
||||
fontSize={'xs'}
|
||||
border={'sm'}
|
||||
borderRadius={'sm'}
|
||||
_hover={{
|
||||
'.controller': {
|
||||
display: 'flex'
|
||||
}
|
||||
}}
|
||||
overflow={'hidden'}
|
||||
display={'inline-flex'}
|
||||
height={6}
|
||||
>
|
||||
<Flex
|
||||
color={'myGray.500'}
|
||||
bg={'myGray.150'}
|
||||
w={4}
|
||||
justifyContent={'center'}
|
||||
fontSize={'10px'}
|
||||
h={'full'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
{index + 1}
|
||||
</Flex>
|
||||
<Flex px={1.5}>
|
||||
<MyIcon name={icon as any} mr={1} flexShrink={0} w={'12px'} />
|
||||
<Box
|
||||
className="textEllipsis3"
|
||||
wordBreak={'break-all'}
|
||||
flex={'1 0 0'}
|
||||
fontSize={'mini'}
|
||||
color={'myGray.900'}
|
||||
>
|
||||
{sourceName}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
{score && !isDeleted && (
|
||||
<Box className="hover-data" visibility={'hidden'}>
|
||||
<ScoreTag {...score} />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
{!isDeleted ? (
|
||||
<>
|
||||
<Markdown source={q} />
|
||||
{!!a && (
|
||||
<Box>
|
||||
<Markdown source={a} />
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Flex
|
||||
justifyContent={'center'}
|
||||
alignItems={'center'}
|
||||
h={'full'}
|
||||
py={2}
|
||||
bg={'#FAFAFA'}
|
||||
color={'myGray.500'}
|
||||
>
|
||||
<MyIcon name="common/info" w={'14px'} mr={1} color={'myGray.500'} />
|
||||
{t('chat:chat.quote.deleted')}
|
||||
</Flex>
|
||||
)}
|
||||
<Flex
|
||||
className="hover-data"
|
||||
position={'absolute'}
|
||||
bottom={2}
|
||||
right={5}
|
||||
gap={1.5}
|
||||
visibility={'hidden'}
|
||||
>
|
||||
<MyTooltip label={t('common:core.dataset.Quote Length')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'10px'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'white'}
|
||||
rounded={'sm'}
|
||||
px={2}
|
||||
py={1}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
>
|
||||
<MyIcon name="common/text/t" w={'14px'} mr={1} color={'myGray.500'} />
|
||||
{q.length + (a?.length || 0)}
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
<MyTooltip label={t('common:common.Copy')}>
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
fontSize={'10px'}
|
||||
border={'1px solid'}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'white'}
|
||||
rounded={'sm'}
|
||||
px={1}
|
||||
py={1}
|
||||
boxShadow={
|
||||
'0px 1px 2px 0px rgba(19, 51, 107, 0.05), 0px 0px 1px 0px rgba(19, 51, 107, 0.08)'
|
||||
}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
copyData(q + '\n' + a);
|
||||
}}
|
||||
>
|
||||
<MyIcon name="copy" w={'14px'} color={'myGray.500'} />
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuoteItem;
|
||||
@@ -0,0 +1,161 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import QuoteItem from './QuoteItem';
|
||||
import { useMemo } from 'react';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import { formatScore } from '@/components/core/dataset/QuoteItem';
|
||||
import { GetAllQuoteDataProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import { getQuoteDataList } from '@/web/core/chat/api';
|
||||
|
||||
const QuoteReader = ({
|
||||
rawSearch,
|
||||
metadata,
|
||||
onClose
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
metadata: GetAllQuoteDataProps;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const filterRawSearch = useMemo(() => {
|
||||
return rawSearch.filter((item) => metadata.collectionIdList.includes(item.collectionId));
|
||||
}, [rawSearch, metadata.collectionIdList]);
|
||||
|
||||
const { data: quoteList, loading } = useRequest2(
|
||||
async () =>
|
||||
await getQuoteDataList({
|
||||
datasetDataIdList: filterRawSearch.map((item) => item.id),
|
||||
collectionIdList: metadata.collectionIdList,
|
||||
chatItemDataId: metadata.chatItemDataId,
|
||||
appId: metadata.appId,
|
||||
chatId: metadata.chatId,
|
||||
...metadata.outLinkAuthData
|
||||
}),
|
||||
{
|
||||
refreshDeps: [metadata, filterRawSearch],
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const formatedDataList = useMemo(() => {
|
||||
return filterRawSearch
|
||||
.map((searchItem) => {
|
||||
const dataItem = quoteList?.find((item) => item._id === searchItem.id);
|
||||
|
||||
return {
|
||||
id: searchItem.id,
|
||||
q: dataItem?.q || 'Can not find Data',
|
||||
a: dataItem?.a || '',
|
||||
score: formatScore(searchItem.score),
|
||||
sourceName: searchItem?.sourceName || '',
|
||||
icon: getSourceNameIcon({
|
||||
sourceId: searchItem.sourceId,
|
||||
sourceName: searchItem.sourceName
|
||||
})
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return (b.score.primaryScore?.value || 0) - (a.score.primaryScore?.value || 0);
|
||||
});
|
||||
}, [quoteList, filterRawSearch]);
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} h={'full'}>
|
||||
{/* title */}
|
||||
<Flex
|
||||
w={'full'}
|
||||
alignItems={'center'}
|
||||
px={5}
|
||||
borderBottom={'1px solid'}
|
||||
borderColor={'myGray.150'}
|
||||
>
|
||||
<Box flex={1} py={4}>
|
||||
<Flex gap={2} mr={2} mb={1}>
|
||||
{metadata.sourceId ? (
|
||||
<>
|
||||
<MyIcon
|
||||
name={
|
||||
getSourceNameIcon({
|
||||
sourceId: metadata.sourceId,
|
||||
sourceName: metadata.sourceName || ''
|
||||
}) as any
|
||||
}
|
||||
w={['1rem', '1.25rem']}
|
||||
color={'primary.600'}
|
||||
/>
|
||||
<Box
|
||||
ml={1}
|
||||
maxW={['200px', '220px']}
|
||||
className={'textEllipsis'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
color={'myGray.900'}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{metadata.sourceName || t('common:common.UnKnow Source')}
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<MyIcon
|
||||
name={'core/chat/quoteFill'}
|
||||
w={['1rem', '1.25rem']}
|
||||
color={'primary.600'}
|
||||
/>
|
||||
<Box
|
||||
maxW={['200px', '300px']}
|
||||
className={'textEllipsis'}
|
||||
wordBreak={'break-all'}
|
||||
color={'myGray.900'}
|
||||
fontWeight={'medium'}
|
||||
>
|
||||
{t('common:core.chat.Quote Amount', { amount: filterRawSearch.length })}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
<Box fontSize={'mini'} color={'myGray.500'}>
|
||||
{t('common:core.chat.quote.Quote Tip')}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
cursor={'pointer'}
|
||||
borderRadius={'sm'}
|
||||
p={1}
|
||||
_hover={{
|
||||
bg: 'myGray.100'
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
<MyIcon name="common/closeLight" color={'myGray.900'} w={6} />
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
{/* quote list */}
|
||||
<MyBox flex={'1 0 0'} mt={2} px={5} py={1} overflow={'auto'} isLoading={loading}>
|
||||
{!loading && (
|
||||
<Flex flexDir={'column'} gap={3}>
|
||||
{formatedDataList?.map((item, index) => (
|
||||
<QuoteItem
|
||||
key={item.id}
|
||||
index={index}
|
||||
icon={item.icon}
|
||||
sourceName={item.sourceName}
|
||||
score={item.score}
|
||||
q={item.q}
|
||||
a={item.a}
|
||||
/>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
</MyBox>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuoteReader;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ScoreItemType, scoreTheme } from '@/components/core/dataset/QuoteItem';
|
||||
import { Box, Flex, Progress } from '@chakra-ui/react';
|
||||
import { SearchScoreTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
const ScoreTag = (score: { primaryScore?: ScoreItemType; secondaryScore: ScoreItemType[] }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={3}>
|
||||
{score?.primaryScore && (
|
||||
<MyTooltip
|
||||
label={
|
||||
score.secondaryScore.length ? (
|
||||
<Flex flexDir={'column'} gap={4}>
|
||||
{score.secondaryScore.map((item, i) => (
|
||||
<Box fontSize={'sm'} key={i}>
|
||||
<Flex alignItems={'flex-start'} lineHeight={1.2} mb={1}>
|
||||
<Box
|
||||
px={'5px'}
|
||||
borderWidth={'1px'}
|
||||
borderRadius={'sm'}
|
||||
mr={'2px'}
|
||||
{...(scoreTheme[i] && scoreTheme[i])}
|
||||
>
|
||||
<Box transform={'scale(0.9)'}>#{item.index + 1}</Box>
|
||||
</Box>
|
||||
<Box transform={'scale(0.9)'}>
|
||||
{t(SearchScoreTypeMap[item.type]?.label as any)}: {item.value.toFixed(4)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box h={'4px'}>
|
||||
{SearchScoreTypeMap[item.type]?.showScore && (
|
||||
<Progress
|
||||
value={item.value * 100}
|
||||
h={'4px'}
|
||||
w={'100%'}
|
||||
size="sm"
|
||||
borderRadius={'20px'}
|
||||
{...(scoreTheme[i] && {
|
||||
colorScheme: scoreTheme[i].colorScheme
|
||||
})}
|
||||
bg="#E8EBF0"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
t(SearchScoreTypeMap[score.primaryScore.type]?.desc as any)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Flex
|
||||
borderRadius={'sm'}
|
||||
py={1}
|
||||
px={2}
|
||||
color={'green.600'}
|
||||
bg={'green.50'}
|
||||
alignItems={'center'}
|
||||
fontSize={'11px'}
|
||||
>
|
||||
<Box>
|
||||
{t(SearchScoreTypeMap[score.primaryScore.type]?.label as any)}
|
||||
{SearchScoreTypeMap[score.primaryScore.type]?.showScore
|
||||
? ` ${score.primaryScore.value.toFixed(4)}`
|
||||
: `: ${score.primaryScore.index + 1}`}
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScoreTag;
|
||||
28
projects/app/src/pageComponents/chat/ChatQuoteList/index.tsx
Normal file
28
projects/app/src/pageComponents/chat/ChatQuoteList/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import { GetQuoteProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import CollectionQuoteReader from './CollectionQuoteReader';
|
||||
import QuoteReader from './QuoteReader';
|
||||
|
||||
const ChatQuoteList = ({
|
||||
rawSearch = [],
|
||||
metadata,
|
||||
onClose
|
||||
}: {
|
||||
rawSearch: SearchDataResponseItemType[];
|
||||
metadata: GetQuoteProps;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{'collectionId' in metadata && (
|
||||
<CollectionQuoteReader rawSearch={rawSearch} metadata={metadata} onClose={onClose} />
|
||||
)}
|
||||
{'collectionIdList' in metadata && (
|
||||
<QuoteReader rawSearch={rawSearch} metadata={metadata} onClose={onClose} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatQuoteList;
|
||||
@@ -60,7 +60,7 @@ const ApiDatasetForm = ({
|
||||
<Input
|
||||
bg={'myWhite.600'}
|
||||
placeholder={t('dataset:request_headers')}
|
||||
maxLength={200}
|
||||
maxLength={2000}
|
||||
{...register('apiServer.authorization')}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
delOneDatasetDataById,
|
||||
getDatasetCollectionById
|
||||
} from '@/web/core/dataset/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
|
||||
@@ -113,7 +113,7 @@ const DatasetImportContextProvider = ({ children }: { children: React.ReactNode
|
||||
],
|
||||
[ImportDataSourceEnum.fileLink]: [
|
||||
{
|
||||
title: t('dataset:import_select_file')
|
||||
title: t('dataset:import_select_link')
|
||||
},
|
||||
{
|
||||
title: t('dataset:import_param_setting')
|
||||
|
||||
@@ -36,19 +36,19 @@ import MyNumberInput from '@fastgpt/web/components/common/Input/NumberInput';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { shadowLight } from '@fastgpt/web/styles/theme';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
|
||||
function DataProcess() {
|
||||
const { t } = useTranslation();
|
||||
const { feConfigs } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { goToNext, processParamsForm, chunkSizeField, minChunkSize, maxChunkSize } =
|
||||
useContextSelector(DatasetImportContext, (v) => v);
|
||||
const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail);
|
||||
const { getValues, setValue, register, watch } = processParamsForm;
|
||||
const { setValue, register, watch } = processParamsForm;
|
||||
|
||||
const trainingType = watch('trainingType');
|
||||
const chunkSettingMode = watch('chunkSettingMode');
|
||||
const qaPrompt = watch('qaPrompt');
|
||||
|
||||
const {
|
||||
isOpen: isOpenCustomPrompt,
|
||||
@@ -65,7 +65,7 @@ function DataProcess() {
|
||||
value: key as DatasetCollectionDataProcessModeEnum,
|
||||
tooltip: t(value.tooltip as any)
|
||||
}));
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const Title = useCallback(({ title }: { title: string }) => {
|
||||
return (
|
||||
@@ -159,23 +159,36 @@ function DataProcess() {
|
||||
gridTemplateColumns={'repeat(2, 1fr)'}
|
||||
/>
|
||||
</Box>
|
||||
{trainingType === DatasetCollectionDataProcessModeEnum.chunk && feConfigs?.isPlus && (
|
||||
{trainingType === DatasetCollectionDataProcessModeEnum.chunk && (
|
||||
<Box mt={6}>
|
||||
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
|
||||
{t('dataset:enhanced_indexes')}
|
||||
</Box>
|
||||
<HStack gap={[3, 7]}>
|
||||
<HStack flex={'1'} spacing={1}>
|
||||
<Checkbox {...register('autoIndexes')}>
|
||||
<FormLabel>{t('dataset:auto_indexes')}</FormLabel>
|
||||
</Checkbox>
|
||||
<MyTooltip
|
||||
label={!feConfigs?.isPlus ? t('common:commercial_function_tip') : ''}
|
||||
>
|
||||
<Checkbox isDisabled={!feConfigs?.isPlus} {...register('autoIndexes')}>
|
||||
<FormLabel>{t('dataset:auto_indexes')}</FormLabel>
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
<QuestionTip label={t('dataset:auto_indexes_tips')} />
|
||||
</HStack>
|
||||
<HStack flex={'1'} spacing={1}>
|
||||
<MyTooltip
|
||||
label={!datasetDetail?.vlmModel ? t('common:error_vlm_not_config') : ''}
|
||||
label={
|
||||
!feConfigs?.isPlus
|
||||
? t('common:commercial_function_tip')
|
||||
: !datasetDetail?.vlmModel
|
||||
? t('common:error_vlm_not_config')
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<Checkbox isDisabled={!datasetDetail?.vlmModel} {...register('imageIndex')}>
|
||||
<Checkbox
|
||||
isDisabled={!feConfigs?.isPlus || !datasetDetail?.vlmModel}
|
||||
{...register('imageIndex')}
|
||||
>
|
||||
<FormLabel>{t('dataset:image_auto_parse')}</FormLabel>
|
||||
</Checkbox>
|
||||
</MyTooltip>
|
||||
@@ -271,7 +284,7 @@ function DataProcess() {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{getValues('qaPrompt')}
|
||||
{qaPrompt}
|
||||
|
||||
<Box
|
||||
display={'none'}
|
||||
@@ -320,44 +333,6 @@ function DataProcess() {
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
|
||||
{/* <AccordionItem mt={4} border={'none'}>
|
||||
<Title title={t('dataset:import_model_config')} />
|
||||
<AccordionPanel p={2} fontSize={'sm'}>
|
||||
<Box>
|
||||
<Box>{t('common:core.ai.model.Dataset Agent Model')}</Box>
|
||||
<Box mt={1}>
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={llmModel}
|
||||
list={datasetModelList.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.model
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
setValue('llmModel', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box pt={5}>
|
||||
<Box>{t('dataset:vllm_model')}</Box>
|
||||
<Box mt={1}>
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={vlmModel}
|
||||
list={vllmModelList.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.model
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
setValue('vlmModel', e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</AccordionPanel>
|
||||
</AccordionItem> */}
|
||||
|
||||
<Flex mt={5} gap={3} justifyContent={'flex-end'}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -372,7 +347,7 @@ function DataProcess() {
|
||||
|
||||
{isOpenCustomPrompt && (
|
||||
<PromptTextarea
|
||||
defaultValue={getValues('qaPrompt')}
|
||||
defaultValue={qaPrompt}
|
||||
onChange={(e) => {
|
||||
setValue('qaPrompt', e);
|
||||
}}
|
||||
|
||||
@@ -21,6 +21,7 @@ const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), {
|
||||
loading: () => <Loading fixed={false} />
|
||||
});
|
||||
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
||||
const PreviewData = dynamic(() => import('../commonProgress/PreviewData'));
|
||||
|
||||
const APIDatasetCollection = () => {
|
||||
const activeStep = useContextSelector(DatasetImportContext, (v) => v.activeStep);
|
||||
@@ -29,7 +30,8 @@ const APIDatasetCollection = () => {
|
||||
<>
|
||||
{activeStep === 0 && <CustomAPIFileInput />}
|
||||
{activeStep === 1 && <DataProcess />}
|
||||
{activeStep === 2 && <Upload />}
|
||||
{activeStep === 2 && <PreviewData />}
|
||||
{activeStep === 3 && <Upload />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), {
|
||||
loading: () => <Loading fixed={false} />
|
||||
});
|
||||
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
||||
const PreviewData = dynamic(() => import('../commonProgress/PreviewData'));
|
||||
|
||||
const ExternalFileCollection = () => {
|
||||
const activeStep = useContextSelector(DatasetImportContext, (v) => v.activeStep);
|
||||
@@ -35,7 +36,8 @@ const ExternalFileCollection = () => {
|
||||
<>
|
||||
{activeStep === 0 && <CustomLinkInput />}
|
||||
{activeStep === 1 && <DataProcess />}
|
||||
{activeStep === 2 && <Upload />}
|
||||
{activeStep === 2 && <PreviewData />}
|
||||
{activeStep === 3 && <Upload />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), {
|
||||
loading: () => <Loading fixed={false} />
|
||||
});
|
||||
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
||||
const PreviewData = dynamic(() => import('../commonProgress/PreviewData'));
|
||||
|
||||
const CustomTet = () => {
|
||||
const activeStep = useContextSelector(DatasetImportContext, (v) => v.activeStep);
|
||||
@@ -20,7 +21,8 @@ const CustomTet = () => {
|
||||
<>
|
||||
{activeStep === 0 && <CustomTextInput />}
|
||||
{activeStep === 1 && <DataProcess />}
|
||||
{activeStep === 2 && <Upload />}
|
||||
{activeStep === 2 && <PreviewData />}
|
||||
{activeStep === 3 && <Upload />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ const DataProcess = dynamic(() => import('../commonProgress/DataProcess'), {
|
||||
loading: () => <Loading fixed={false} />
|
||||
});
|
||||
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
||||
const PreviewData = dynamic(() => import('../commonProgress/PreviewData'));
|
||||
|
||||
const LinkCollection = () => {
|
||||
const activeStep = useContextSelector(DatasetImportContext, (v) => v.activeStep);
|
||||
@@ -24,7 +25,8 @@ const LinkCollection = () => {
|
||||
<>
|
||||
{activeStep === 0 && <CustomLinkImport />}
|
||||
{activeStep === 1 && <DataProcess />}
|
||||
{activeStep === 2 && <Upload />}
|
||||
{activeStep === 2 && <PreviewData />}
|
||||
{activeStep === 3 && <Upload />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ const ReTraining = () => {
|
||||
qaChunkSize: collection.chunkSize,
|
||||
customSplitChar: collection.chunkSplitter,
|
||||
qaPrompt: collection.qaPrompt,
|
||||
webSelector: collection.metadata?.webSelector
|
||||
webSelector: collection.metadata?.webPageSelector
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -195,7 +195,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
label: item.name,
|
||||
value: item.model
|
||||
}))}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
const vectorModel = embeddingModelList.find((item) => item.model === e);
|
||||
if (!vectorModel) return;
|
||||
return onOpenConfirmRebuild(async () => {
|
||||
@@ -220,7 +220,7 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
value: item.model
|
||||
}))}
|
||||
fontSize={'mini'}
|
||||
onchange={(e) => {
|
||||
onChange={(e) => {
|
||||
const agentModel = datasetModelList.find((item) => item.model === e);
|
||||
if (!agentModel) return;
|
||||
setValue('agentModel', agentModel);
|
||||
@@ -230,30 +230,28 @@ const Info = ({ datasetId }: { datasetId: string }) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{feConfigs?.isPlus && (
|
||||
<Box pt={5}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('dataset:vllm_model')}
|
||||
</FormLabel>
|
||||
<Box pt={2}>
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={vlmModel?.model}
|
||||
list={vllmModelList.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.model
|
||||
}))}
|
||||
fontSize={'mini'}
|
||||
onchange={(e) => {
|
||||
const vlmModel = vllmModelList.find((item) => item.model === e);
|
||||
if (!vlmModel) return;
|
||||
setValue('vlmModel', vlmModel);
|
||||
return handleSubmit((data) => onSave({ ...data, vlmModel }))();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box pt={5}>
|
||||
<FormLabel fontSize={'mini'} fontWeight={'500'}>
|
||||
{t('dataset:vllm_model')}
|
||||
</FormLabel>
|
||||
<Box pt={2}>
|
||||
<AIModelSelector
|
||||
w={'100%'}
|
||||
value={vlmModel?.model}
|
||||
list={vllmModelList.map((item) => ({
|
||||
label: item.name,
|
||||
value: item.model
|
||||
}))}
|
||||
fontSize={'mini'}
|
||||
onChange={(e) => {
|
||||
const vlmModel = vllmModelList.find((item) => item.model === e);
|
||||
if (!vlmModel) return;
|
||||
setValue('vlmModel', vlmModel);
|
||||
return handleSubmit((data) => onSave({ ...data, vlmModel }))();
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex alignItems={'center'} pt={5}>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Box, Flex, Button, Textarea } from '@chakra-ui/react';
|
||||
import {
|
||||
FieldArrayWithId,
|
||||
UseFieldArrayRemove,
|
||||
UseFormRegister,
|
||||
useFieldArray,
|
||||
useForm
|
||||
} from 'react-hook-form';
|
||||
import { Box, Flex, Button, Textarea, ModalFooter, HStack, VStack } from '@chakra-ui/react';
|
||||
import { UseFormRegister, useFieldArray, useForm } from 'react-hook-form';
|
||||
import {
|
||||
postInsertData2Dataset,
|
||||
putDatasetDataById,
|
||||
@@ -17,36 +11,36 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import { DatasetDataIndexItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import DeleteIcon from '@fastgpt/web/components/common/Icon/delete';
|
||||
import { defaultCollectionDetail } from '@/web/core/dataset/constants';
|
||||
import { getDocPath } from '@/web/common/system/doc';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import styles from './styles.module.scss';
|
||||
import {
|
||||
DatasetDataIndexTypeEnum,
|
||||
getDatasetIndexMapData
|
||||
} from '@fastgpt/global/core/dataset/data/constants';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
|
||||
export type InputDataType = {
|
||||
q: string;
|
||||
a: string;
|
||||
indexes: (Omit<DatasetDataIndexItemType, 'dataId'> & {
|
||||
dataId?: string; // pg data id
|
||||
fold: boolean;
|
||||
})[];
|
||||
};
|
||||
|
||||
enum TabEnum {
|
||||
content = 'content',
|
||||
index = 'index'
|
||||
chunk = 'chunk',
|
||||
qa = 'qa'
|
||||
}
|
||||
|
||||
const InputDataModal = ({
|
||||
@@ -64,73 +58,47 @@ const InputDataModal = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [currentTab, setCurrentTab] = useState(TabEnum.content);
|
||||
const { embeddingModelList, defaultModels } = useSystemStore();
|
||||
|
||||
const [currentTab, setCurrentTab] = useState(TabEnum.chunk);
|
||||
|
||||
const { register, handleSubmit, reset, control } = useForm<InputDataType>();
|
||||
const {
|
||||
fields: indexes,
|
||||
append: appendIndexes,
|
||||
remove: removeIndexes
|
||||
prepend: prependIndexes,
|
||||
remove: removeIndexes,
|
||||
update: updateIndexes
|
||||
} = useFieldArray({
|
||||
control,
|
||||
name: 'indexes'
|
||||
});
|
||||
|
||||
const tabList = [
|
||||
{
|
||||
label: (
|
||||
<Flex align={'center'}>
|
||||
<Box>{t('common:dataset.data.edit.divide_content')}</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: TabEnum.content
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Flex align={'center'}>
|
||||
<Box>{t('common:dataset.data.edit.Index', { amount: indexes.length })}</Box>
|
||||
<MyTooltip label={t('common:core.app.tool_label.view_doc')}>
|
||||
<MyIcon
|
||||
name={'book'}
|
||||
w={'1rem'}
|
||||
mr={'0.38rem'}
|
||||
color={'myGray.500'}
|
||||
ml={1}
|
||||
onClick={() =>
|
||||
window.open(getDocPath('/docs/guide/knowledge_base/dataset_engine/'), '_blank')
|
||||
}
|
||||
_hover={{
|
||||
color: 'primary.600',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
),
|
||||
value: TabEnum.index
|
||||
}
|
||||
];
|
||||
|
||||
const { data: collection = defaultCollectionDetail } = useQuery(
|
||||
['loadCollectionId', collectionId],
|
||||
const { data: collection = defaultCollectionDetail } = useRequest2(
|
||||
() => {
|
||||
return getDatasetCollectionById(collectionId);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [collectionId]
|
||||
}
|
||||
);
|
||||
const { isFetching: isFetchingData } = useQuery(
|
||||
['getDatasetDataItemById', dataId],
|
||||
() => {
|
||||
const { loading: isFetchingData } = useRequest2(
|
||||
async () => {
|
||||
if (dataId) return getDatasetDataItemById(dataId);
|
||||
return null;
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [dataId],
|
||||
onSuccess(res) {
|
||||
if (res) {
|
||||
reset({
|
||||
q: res.q,
|
||||
a: res.a,
|
||||
indexes: res.indexes
|
||||
indexes: res.indexes.map((item) => ({
|
||||
...item,
|
||||
fold: true
|
||||
}))
|
||||
});
|
||||
} else if (defaultValue) {
|
||||
reset({
|
||||
@@ -138,6 +106,10 @@ const InputDataModal = ({
|
||||
a: defaultValue.a
|
||||
});
|
||||
}
|
||||
|
||||
if (res?.a || defaultValue?.a) {
|
||||
setCurrentTab(TabEnum.qa);
|
||||
}
|
||||
},
|
||||
onError(err) {
|
||||
toast({
|
||||
@@ -161,7 +133,6 @@ const InputDataModal = ({
|
||||
const { runAsync: sureImportData, loading: isImporting } = useRequest2(
|
||||
async (e: InputDataType) => {
|
||||
if (!e.q) {
|
||||
setCurrentTab(TabEnum.content);
|
||||
return Promise.reject(t('common:dataset.data.input is empty'));
|
||||
}
|
||||
|
||||
@@ -175,9 +146,9 @@ const InputDataModal = ({
|
||||
const dataId = await postInsertData2Dataset({
|
||||
collectionId: collection._id,
|
||||
q: e.q,
|
||||
a: e.a,
|
||||
a: currentTab === TabEnum.qa ? e.a : '',
|
||||
// Contains no default index
|
||||
indexes: e.indexes
|
||||
indexes: e.indexes.filter((item) => !!item.text?.trim())
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -186,6 +157,7 @@ const InputDataModal = ({
|
||||
};
|
||||
},
|
||||
{
|
||||
refreshDeps: [currentTab],
|
||||
successToast: t('common:dataset.data.Input Success Tip'),
|
||||
onSuccess(e) {
|
||||
reset({
|
||||
@@ -205,11 +177,11 @@ const InputDataModal = ({
|
||||
async (e: InputDataType) => {
|
||||
if (!dataId) return Promise.reject(t('common:common.error.unKnow'));
|
||||
|
||||
// not exactly same
|
||||
await putDatasetDataById({
|
||||
dataId,
|
||||
...e,
|
||||
indexes: e.indexes
|
||||
q: e.q,
|
||||
a: currentTab === TabEnum.qa ? e.a : '',
|
||||
indexes: e.indexes.filter((item) => !!item.text?.trim())
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -218,6 +190,7 @@ const InputDataModal = ({
|
||||
};
|
||||
},
|
||||
{
|
||||
refreshDeps: [currentTab],
|
||||
successToast: t('common:dataset.data.Update Success Tip'),
|
||||
onSuccess(data) {
|
||||
onSuccess(data);
|
||||
@@ -267,49 +240,174 @@ const InputDataModal = ({
|
||||
isLoading={isLoading}
|
||||
h={'100%'}
|
||||
py={[6, '1.5rem']}
|
||||
px={[5, '3.25rem']}
|
||||
>
|
||||
<Flex justify={'space-between'} gap={4} w={'100%'}>
|
||||
<Flex justify={'space-between'} pb={4}>
|
||||
<LightRowTabs<TabEnum>
|
||||
list={tabList}
|
||||
p={0}
|
||||
value={currentTab}
|
||||
onChange={(e: TabEnum) => setCurrentTab(e)}
|
||||
/>
|
||||
</Flex>
|
||||
{currentTab === TabEnum.index && (
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
boxShadow={'1'}
|
||||
p={0}
|
||||
onClick={() =>
|
||||
appendIndexes({
|
||||
type: DatasetDataIndexTypeEnum.custom,
|
||||
text: ''
|
||||
})
|
||||
}
|
||||
>
|
||||
<Flex px={'0.62rem'} py={2}>
|
||||
<MyIcon name={'common/addLight'} w={'1rem'} mr={'0.38rem'} />
|
||||
{t('common:add_new')}
|
||||
</Flex>
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
<Box w={'100%'} flexGrow={1} overflow={'scroll'}>
|
||||
{currentTab === TabEnum.content && <InputTab maxToken={maxToken} register={register} />}
|
||||
{currentTab === TabEnum.index && (
|
||||
<DataIndex
|
||||
register={register}
|
||||
maxToken={maxToken}
|
||||
removeIndexes={removeIndexes}
|
||||
indexes={indexes}
|
||||
/>
|
||||
)}
|
||||
{/* Tab */}
|
||||
<Box px={[5, '3.25rem']}>
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{ label: t('common:dataset_data_input_chunk'), value: TabEnum.chunk },
|
||||
{ label: t('common:dataset_data_input_qa'), value: TabEnum.qa }
|
||||
]}
|
||||
py={1}
|
||||
value={currentTab}
|
||||
onChange={(e) => {
|
||||
setCurrentTab(e);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Flex justifyContent={'flex-end'} pt={8} pb={[8, 0]} h={[24, 16]}>
|
||||
<Flex flex={'1 0 0'} h={['auto', '0']} gap={6} flexDir={['column', 'row']} px={[5, '0']}>
|
||||
{/* Data */}
|
||||
<Flex
|
||||
pt={4}
|
||||
pl={[0, '3.25rem']}
|
||||
flexDir={'column'}
|
||||
h={'100%'}
|
||||
gap={3}
|
||||
flex={'1 0 0'}
|
||||
w={['100%', 0]}
|
||||
overflow={['unset', 'auto']}
|
||||
>
|
||||
<Flex flexDir={'column'} h={'100%'}>
|
||||
<FormLabel required mb={1} h={'30px'}>
|
||||
{currentTab === TabEnum.chunk
|
||||
? t('common:dataset_data_input_chunk_content')
|
||||
: t('common:dataset_data_input_q')}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
resize={'none'}
|
||||
placeholder={t('common:dataset_data_import_q_placeholder', { maxToken })}
|
||||
className={styles.scrollbar}
|
||||
maxLength={maxToken}
|
||||
flex={'1 0 0'}
|
||||
tabIndex={1}
|
||||
_focus={{
|
||||
borderColor: 'primary.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
bg: 'white'
|
||||
}}
|
||||
bg={'myGray.25'}
|
||||
borderRadius={'md'}
|
||||
borderColor={'myGray.200'}
|
||||
{...register(`q`, {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
{currentTab === TabEnum.qa && (
|
||||
<Flex flexDir={'column'} h={'100%'}>
|
||||
<FormLabel required mb={1}>
|
||||
{t('common:dataset_data_input_a')}
|
||||
</FormLabel>
|
||||
<Textarea
|
||||
resize={'none'}
|
||||
placeholder={t('common:dataset_data_import_q_placeholder', { maxToken })}
|
||||
className={styles.scrollbar}
|
||||
flex={'1 0 0'}
|
||||
tabIndex={1}
|
||||
bg={'myGray.25'}
|
||||
maxLength={maxToken}
|
||||
borderRadius={'md'}
|
||||
border={'1.5px solid '}
|
||||
borderColor={'myGray.200'}
|
||||
{...register('a', { required: true })}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{/* Index */}
|
||||
<Box
|
||||
pt={4}
|
||||
pr={[0, '3.25rem']}
|
||||
flex={'1 0 0'}
|
||||
w={['100%', 0]}
|
||||
overflow={['unset', 'auto']}
|
||||
>
|
||||
<Flex alignItems={'flex-start'} justifyContent={'space-between'} h={'30px'}>
|
||||
<FormLabel>
|
||||
{t('common:dataset.data.edit.Index', {
|
||||
amount: indexes.length
|
||||
})}
|
||||
</FormLabel>
|
||||
<Button
|
||||
variant={'whiteBase'}
|
||||
size={'sm'}
|
||||
p={0}
|
||||
transform={'translateY(-6px)'}
|
||||
onClick={() =>
|
||||
prependIndexes({
|
||||
type: DatasetDataIndexTypeEnum.custom,
|
||||
text: '',
|
||||
fold: false
|
||||
})
|
||||
}
|
||||
>
|
||||
<Flex px={'0.62rem'} py={2}>
|
||||
<MyIcon name={'common/addLight'} w={'1rem'} mr={'0.38rem'} />
|
||||
{t('common:add_new')}
|
||||
</Flex>
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<VStack>
|
||||
{indexes?.map((index, i) => {
|
||||
const data = getDatasetIndexMapData(index.type);
|
||||
return (
|
||||
<Box
|
||||
key={index.dataId || i}
|
||||
p={4}
|
||||
borderRadius={'md'}
|
||||
border={'base'}
|
||||
bg={'myGray.25'}
|
||||
w={'100%'}
|
||||
_hover={{
|
||||
'& .delete': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Header */}
|
||||
<Flex mb={2} alignItems={'center'}>
|
||||
<FormLabel flex={'1 0 0'}>{t(data.label)}</FormLabel>
|
||||
{/* Delete */}
|
||||
{index.type !== 'default' && (
|
||||
<HStack className={'delete'} borderRight={'base'} pr={3} mr={2}>
|
||||
<DeleteIcon
|
||||
onClick={() => {
|
||||
removeIndexes(i);
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
)}
|
||||
{indexes.length > 1 && (
|
||||
<MyIconButton
|
||||
icon={index.fold ? 'core/chat/chevronDown' : 'core/chat/chevronUp'}
|
||||
onClick={() => {
|
||||
updateIndexes(i, { ...index, fold: !index.fold });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
{/* Content */}
|
||||
<DataIndexTextArea
|
||||
disabled={index.type === 'default'}
|
||||
index={i}
|
||||
value={index.text}
|
||||
isFolder={index.fold && indexes.length > 1}
|
||||
maxToken={maxToken}
|
||||
register={register}
|
||||
onFocus={() => {
|
||||
updateIndexes(i, { ...index, fold: false });
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</VStack>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<ModalFooter px={[5, '3.25rem']} py={0} pt={4}>
|
||||
<MyTooltip
|
||||
label={collection.permission.hasWritePer ? '' : t('common:dataset.data.Can not edit')}
|
||||
>
|
||||
@@ -322,7 +420,7 @@ const InputDataModal = ({
|
||||
{dataId ? t('common:common.Confirm Update') : t('common:common.Confirm Import')}
|
||||
</Button>
|
||||
</MyTooltip>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</MyBox>
|
||||
</MyModal>
|
||||
);
|
||||
@@ -330,153 +428,23 @@ const InputDataModal = ({
|
||||
|
||||
export default React.memo(InputDataModal);
|
||||
|
||||
const InputTab = ({
|
||||
maxToken,
|
||||
register
|
||||
}: {
|
||||
maxToken: number;
|
||||
register: UseFormRegister<InputDataType>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex h={'100%'} gap={6} flexDir={['column', 'row']} w={'100%'}>
|
||||
<Flex flexDir={'column'} flex={1}>
|
||||
<Flex mb={2} fontWeight={'medium'} fontSize={'sm'} alignItems={'center'} h={8}>
|
||||
<Box color={'red.600'}>*</Box>
|
||||
<Box color={'myGray.900'}>{t('common:core.dataset.data.Main Content')}</Box>
|
||||
<QuestionTip label={t('common:core.dataset.data.Data Content Tip')} ml={1} />
|
||||
</Flex>
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
border={'1.5px solid var(--Gray-Modern-200, #E8EBF0)'}
|
||||
bg={'myGray.25'}
|
||||
flex={1}
|
||||
>
|
||||
<Textarea
|
||||
resize={'none'}
|
||||
placeholder={t('core.dataset.data.Data Content Placeholder', { maxToken })}
|
||||
className={styles.scrollbar}
|
||||
maxLength={maxToken}
|
||||
h={'100%'}
|
||||
tabIndex={1}
|
||||
_focus={{
|
||||
borderColor: 'primary.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
bg: 'white'
|
||||
}}
|
||||
borderColor={'transparent'}
|
||||
bg={'myGray.25'}
|
||||
{...register(`q`, {
|
||||
required: true
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Flex flex={1} flexDir={'column'}>
|
||||
<Flex mb={2} fontWeight={'medium'} fontSize={'sm'} alignItems={'center'} h={8}>
|
||||
<Box color={'myGray.900'}>{t('common:core.dataset.data.Auxiliary Data')}</Box>
|
||||
<QuestionTip label={t('common:core.dataset.data.Auxiliary Data Tip')} ml={1} />
|
||||
</Flex>
|
||||
<Box
|
||||
borderRadius={'md'}
|
||||
border={'1.5px solid '}
|
||||
borderColor={'myGray.200'}
|
||||
bg={'myGray.25'}
|
||||
flex={1}
|
||||
>
|
||||
<Textarea
|
||||
resize={'none'}
|
||||
placeholder={t('core.dataset.data.Auxiliary Data Placeholder', {
|
||||
maxToken: maxToken * 1.5
|
||||
})}
|
||||
className={styles.scrollbar}
|
||||
borderColor={'transparent'}
|
||||
h={'100%'}
|
||||
tabIndex={1}
|
||||
bg={'myGray.25'}
|
||||
maxLength={maxToken * 1.5}
|
||||
{...register('a')}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DataIndex = ({
|
||||
maxToken,
|
||||
register,
|
||||
indexes,
|
||||
removeIndexes
|
||||
}: {
|
||||
maxToken: number;
|
||||
register: UseFormRegister<InputDataType>;
|
||||
indexes: FieldArrayWithId<InputDataType, 'indexes', 'id'>[];
|
||||
removeIndexes: UseFieldArrayRemove;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex mt={3} gap={3} flexDir={'column'}>
|
||||
{indexes?.map((index, i) => {
|
||||
const data = getDatasetIndexMapData(index.type);
|
||||
return (
|
||||
<Box
|
||||
key={index.dataId || i}
|
||||
p={4}
|
||||
borderRadius={'md'}
|
||||
border={'1.5px solid var(--Gray-Modern-200, #E8EBF0)'}
|
||||
bg={'myGray.25'}
|
||||
_hover={{
|
||||
'& .delete': {
|
||||
display: 'block'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex mb={2}>
|
||||
<Box flex={1} fontWeight={'medium'} fontSize={'sm'} color={'myGray.900'}>
|
||||
{t(data.label)}
|
||||
</Box>
|
||||
{index.type !== 'default' && (
|
||||
<DeleteIcon
|
||||
onClick={() => {
|
||||
removeIndexes(i);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
<DataIndexTextArea
|
||||
disabled={index.type === 'default'}
|
||||
index={i}
|
||||
value={index.text}
|
||||
maxToken={maxToken}
|
||||
register={register}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const textareaMinH = '40px';
|
||||
const DataIndexTextArea = ({
|
||||
value,
|
||||
index,
|
||||
maxToken,
|
||||
register,
|
||||
disabled
|
||||
disabled,
|
||||
isFolder,
|
||||
onFocus
|
||||
}: {
|
||||
value: string;
|
||||
index: number;
|
||||
maxToken: number;
|
||||
register: UseFormRegister<InputDataType>;
|
||||
disabled?: boolean;
|
||||
isFolder: boolean;
|
||||
onFocus: () => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const TextareaDom = useRef<HTMLTextAreaElement | null>(null);
|
||||
@@ -501,41 +469,76 @@ const DataIndexTextArea = ({
|
||||
}
|
||||
}, []);
|
||||
|
||||
return disabled ? (
|
||||
<Box fontSize={'sm'} color={'myGray.500'} whiteSpace={'pre-wrap'}>
|
||||
{value}
|
||||
const onclickMark = () => {
|
||||
TextareaDom?.current?.focus();
|
||||
onFocus();
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
pos={'relative'}
|
||||
{...(isFolder
|
||||
? {
|
||||
maxH: '50px',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
: {
|
||||
maxH: 'auto'
|
||||
})}
|
||||
>
|
||||
{disabled ? (
|
||||
<Box fontSize={'sm'} color={'myGray.500'} whiteSpace={'pre-wrap'}>
|
||||
{value}
|
||||
</Box>
|
||||
) : (
|
||||
<Textarea
|
||||
maxLength={maxToken}
|
||||
borderColor={'transparent'}
|
||||
className={styles.scrollbar}
|
||||
minH={textareaMinH}
|
||||
px={0}
|
||||
pt={0}
|
||||
isRequired={required}
|
||||
whiteSpace={'pre-wrap'}
|
||||
resize={'none'}
|
||||
_focus={{
|
||||
px: 3,
|
||||
py: 1,
|
||||
borderColor: 'primary.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
bg: 'white'
|
||||
}}
|
||||
placeholder={t('common:dataset.data.Index Placeholder')}
|
||||
ref={(e) => {
|
||||
if (e) TextareaDom.current = e;
|
||||
TextareaRef(e);
|
||||
}}
|
||||
required
|
||||
name={name}
|
||||
onChange={(e) => {
|
||||
autoHeight(e);
|
||||
onTextChange(e);
|
||||
}}
|
||||
onFocus={autoHeight}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
)}
|
||||
{isFolder && (
|
||||
<Box
|
||||
pos={'absolute'}
|
||||
bottom={0}
|
||||
left={0}
|
||||
right={0}
|
||||
top={0}
|
||||
bg={'linear-gradient(182deg, rgba(251, 251, 252, 0.00) 1.76%, #FBFBFC 84.07%)'}
|
||||
{...(disabled
|
||||
? {}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
onClick: onclickMark
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Textarea
|
||||
maxLength={maxToken}
|
||||
borderColor={'transparent'}
|
||||
className={styles.scrollbar}
|
||||
minH={textareaMinH}
|
||||
px={0}
|
||||
pt={0}
|
||||
isRequired={required}
|
||||
whiteSpace={'pre-wrap'}
|
||||
resize={'none'}
|
||||
_focus={{
|
||||
px: 3,
|
||||
py: 1,
|
||||
borderColor: 'primary.500',
|
||||
boxShadow: '0px 0px 0px 2.4px rgba(51, 112, 255, 0.15)',
|
||||
bg: 'white'
|
||||
}}
|
||||
placeholder={t('common:dataset.data.Index Placeholder')}
|
||||
ref={(e) => {
|
||||
if (e) TextareaDom.current = e;
|
||||
TextareaRef(e);
|
||||
}}
|
||||
required
|
||||
name={name}
|
||||
onChange={(e) => {
|
||||
autoHeight(e);
|
||||
onTextChange(e);
|
||||
}}
|
||||
onFocus={autoHeight}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import { useRouter } from 'next/router';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover';
|
||||
import ParentPaths from '@/components/common/ParentPaths';
|
||||
@@ -22,7 +21,6 @@ export enum TabEnum {
|
||||
const NavBar = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { datasetT } = useI18n();
|
||||
const router = useRouter();
|
||||
const query = router.query;
|
||||
const { isPc } = useSystem();
|
||||
@@ -168,7 +166,7 @@ const NavBar = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
{rebuildingCount > 0 && (
|
||||
<Box mb={3}>
|
||||
<Box fontSize={'sm'}>
|
||||
{datasetT('rebuilding_index_count', { count: rebuildingCount })}
|
||||
{t('dataset:rebuilding_index_count', { count: rebuildingCount })}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user