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:
Archer
2025-03-18 14:40:41 +08:00
committed by GitHub
parent 56793114d8
commit e75d81d05a
316 changed files with 10626 additions and 8464 deletions

View File

@@ -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 && (

View File

@@ -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}

View File

@@ -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'
}

View File

@@ -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(() => {

View File

@@ -26,7 +26,7 @@ const I18nLngSelector = () => {
<MySelect
value={i18n.language}
list={list}
onchange={(val: any) => {
onChange={(val: any) => {
const lang = val;
onChangeLng(lang);
}}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}}
/>

View File

@@ -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>

View File

@@ -77,7 +77,7 @@ const SettingLLMModel = ({
value: item.model,
label: item.name
}))}
onchange={(e) => {
onChange={(e) => {
onChange({
...defaultData,
model: e

View File

@@ -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);
}}
/>

View File

@@ -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();

View File

@@ -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;

View File

@@ -125,7 +125,7 @@ const QGConfigModal = ({
value: item.model,
label: item.name
}))}
onchange={(e) => {
onChange={(e) => {
onChange({
...value,
model: e

View File

@@ -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'}

View File

@@ -59,7 +59,7 @@ type Props = BasicProps & {
const RenderQuestionGuide = ({ questionGuides }: { questionGuides: string[] }) => {
return (
<Markdown
source={`\`\`\`${CodeClassNameEnum.questionGuide}
source={`\`\`\`${CodeClassNameEnum.questionguide}
${JSON.stringify(questionGuides)}`}
/>
);

View File

@@ -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;

View File

@@ -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>
))}
</>
);
});

View File

@@ -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} />
)}
</>
);
};

View File

@@ -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)}
/>
);
}}

View File

@@ -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>
);
};

View File

@@ -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}
/>
);
}

View File

@@ -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>

View File

@@ -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)}
/>
);
}}

View File

@@ -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')} />
)}

View File

@@ -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>
);
};

View File

@@ -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}&currentTab=dataCard&collectionId=${quoteItem.collectionId}`}
>
{t('common:core.dataset.Go Dataset')}
{t('chat:to_dataset')}
<MyIcon name={'common/rightArrowLight'} w={'10px'} />
</Link>
)}

View File

@@ -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,

View File

@@ -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 })}

View File

@@ -49,7 +49,7 @@ const DefaultPermissionList = ({
<MySelect
list={defaultPermissionSelectList}
value={per}
onchange={(per) => {
onChange={(per) => {
if (isInheritPermission && hasParent) {
openConfirm(
() => onRequestChange(per),

View File

@@ -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}>

View File

@@ -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);