fix: ui
This commit is contained in:
parent
3b0f0a8108
commit
e74ab643fe
@ -50,6 +50,7 @@ export type SelectProps<T = any> = Omit<ButtonProps, 'onChange'> & {
|
|||||||
showBorder?: boolean;
|
showBorder?: boolean;
|
||||||
}[];
|
}[];
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
showAvatar?: boolean;
|
||||||
onChange?: (val: T) => any | Promise<any>;
|
onChange?: (val: T) => any | Promise<any>;
|
||||||
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
|
||||||
customOnOpen?: () => void;
|
customOnOpen?: () => void;
|
||||||
@ -79,6 +80,7 @@ const MySelect = <T = any,>(
|
|||||||
list = [],
|
list = [],
|
||||||
onChange,
|
onChange,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
|
showAvatar = true,
|
||||||
ScrollData,
|
ScrollData,
|
||||||
customOnOpen,
|
customOnOpen,
|
||||||
customOnClose,
|
customOnClose,
|
||||||
@ -255,7 +257,7 @@ const MySelect = <T = any,>(
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{selectItem?.icon && (
|
{selectItem?.icon && showAvatar && (
|
||||||
<Avatar
|
<Avatar
|
||||||
mr={2}
|
mr={2}
|
||||||
src={selectItem.icon as any}
|
src={selectItem.icon as any}
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"image_upload": "Image upload",
|
"image_upload": "Image upload",
|
||||||
"no_gate_available": "No portal available",
|
"no_gate_available": "No portal available",
|
||||||
"no_gate_to_delete": "There is no gate to delete",
|
"no_gate_to_delete": "There is no gate to delete",
|
||||||
|
"quick_app": "Quick Application",
|
||||||
"slogan": "slogan",
|
"slogan": "slogan",
|
||||||
"status": "state",
|
"status": "state",
|
||||||
"suggestion_ratio_1_1": "Suggested ratio 1:1",
|
"suggestion_ratio_1_1": "Suggested ratio 1:1",
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"image_upload": "图片上传",
|
"image_upload": "图片上传",
|
||||||
"no_gate_available": "没有可用门户",
|
"no_gate_available": "没有可用门户",
|
||||||
"no_gate_to_delete": "没有可以删除的门户了",
|
"no_gate_to_delete": "没有可以删除的门户了",
|
||||||
|
"quick_app": "快捷应用",
|
||||||
"slogan": "标语",
|
"slogan": "标语",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"suggestion_ratio_1_1": "建议比例 1:1",
|
"suggestion_ratio_1_1": "建议比例 1:1",
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"image_upload": "圖片上傳",
|
"image_upload": "圖片上傳",
|
||||||
"no_gate_available": "沒有可用門戶",
|
"no_gate_available": "沒有可用門戶",
|
||||||
"no_gate_to_delete": "沒有可以刪除的門戶了",
|
"no_gate_to_delete": "沒有可以刪除的門戶了",
|
||||||
|
"quick_app": "快捷應用",
|
||||||
"slogan": "標語",
|
"slogan": "標語",
|
||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"suggestion_ratio_1_1": "建議比例 1:1",
|
"suggestion_ratio_1_1": "建議比例 1:1",
|
||||||
|
|||||||
@ -110,7 +110,14 @@ const OneRowSelector = ({ list, onChange, disableTip, ...props }: Props) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const MultipleRowSelector = ({ list, onChange, disableTip, placeholder, ...props }: Props) => {
|
const MultipleRowSelector = ({
|
||||||
|
list,
|
||||||
|
onChange,
|
||||||
|
disableTip,
|
||||||
|
placeholder,
|
||||||
|
showAvatar = true,
|
||||||
|
...props
|
||||||
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } =
|
const { llmModelList, embeddingModelList, ttsModelList, sttModelList, reRankModelList } =
|
||||||
useSystemStore();
|
useSystemStore();
|
||||||
@ -193,17 +200,20 @@ const MultipleRowSelector = ({ list, onChange, disableTip, placeholder, ...props
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack spacing={1}>
|
<HStack spacing={1}>
|
||||||
<Avatar
|
{showAvatar && (
|
||||||
borderRadius={'0'}
|
<Avatar
|
||||||
mr={2}
|
borderRadius={'0'}
|
||||||
src={modelData?.avatar}
|
mr={2}
|
||||||
fallbackSrc={HUGGING_FACE_ICON}
|
src={modelData?.avatar}
|
||||||
w={avatarSize}
|
fallbackSrc={HUGGING_FACE_ICON}
|
||||||
/>
|
w={avatarSize}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box>{modelData?.name}</Box>
|
<Box>{modelData?.name}</Box>
|
||||||
</HStack>
|
</HStack>
|
||||||
);
|
);
|
||||||
}, [modelList, props.value, t, avatarSize]);
|
}, [modelList, props.value, t, showAvatar, avatarSize]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useRef, useCallback, useMemo, useState, useEffect, useContext } from 'react';
|
import React, { useRef, useCallback, useMemo, useState, useEffect, useContext } from 'react';
|
||||||
import { Box, Flex, Textarea, IconButton, useBreakpointValue, Button } from '@chakra-ui/react';
|
import { Box, Flex, Textarea, IconButton, useBreakpointValue } from '@chakra-ui/react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import { getWebDefaultLLMModel } from '@/web/common/system/utils';
|
import { getWebDefaultLLMModel } from '@/web/common/system/utils';
|
||||||
@ -23,7 +23,8 @@ import dynamic from 'next/dynamic';
|
|||||||
import { AppContext } from '@/pageComponents/app/detail/context';
|
import { AppContext } from '@/pageComponents/app/detail/context';
|
||||||
import { AppFormContext } from '@/pages/chat/gate/index';
|
import { AppFormContext } from '@/pages/chat/gate/index';
|
||||||
import Icon from '@fastgpt/web/components/common/Icon';
|
import Icon from '@fastgpt/web/components/common/Icon';
|
||||||
import GateSelect from '@fastgpt/web/components/common/MySelect/GateSelect';
|
import AIModelSelector from '@/components/Select/AIModelSelector';
|
||||||
|
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
|
|
||||||
const GateToolSelect = dynamic(
|
const GateToolSelect = dynamic(
|
||||||
() => import('@/pageComponents/app/detail/Gate/components/GateToolSelect'),
|
() => import('@/pageComponents/app/detail/Gate/components/GateToolSelect'),
|
||||||
@ -46,8 +47,8 @@ type Props = {
|
|||||||
resetInputVal: (val: ChatBoxInputType) => void;
|
resetInputVal: (val: ChatBoxInputType) => void;
|
||||||
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
chatForm: UseFormReturn<ChatBoxInputFormType>;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
selectedToolIds?: string[];
|
selectedTools?: FlowNodeTemplateType[];
|
||||||
onSelectedToolIdsChange?: (toolIds: string[]) => void;
|
onSelectTools?: (toolIds: FlowNodeTemplateType[]) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GateChatInput = ({
|
const GateChatInput = ({
|
||||||
@ -57,8 +58,8 @@ const GateChatInput = ({
|
|||||||
resetInputVal,
|
resetInputVal,
|
||||||
chatForm,
|
chatForm,
|
||||||
placeholder,
|
placeholder,
|
||||||
selectedToolIds: externalSelectedToolIds,
|
selectedTools: externalSelectedToolIds,
|
||||||
onSelectedToolIdsChange
|
onSelectTools
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isPc } = useSystem();
|
const { isPc } = useSystem();
|
||||||
@ -78,10 +79,9 @@ const GateChatInput = ({
|
|||||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||||
const fileSelectConfig = useContextSelector(ChatBoxContext, (v) => v.fileSelectConfig);
|
const fileSelectConfig = useContextSelector(ChatBoxContext, (v) => v.fileSelectConfig);
|
||||||
|
|
||||||
// 如果有外部传入的工具选择,使用外部的;否则使用内部状态
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const [internalSelectedToolIds, setInternalSelectedToolIds] = useState<string[]>([]);
|
const selectedTools = externalSelectedToolIds ?? [];
|
||||||
const selectedToolIds = externalSelectedToolIds ?? internalSelectedToolIds;
|
const setSelectedToolIds = onSelectTools!;
|
||||||
const setSelectedToolIds = onSelectedToolIdsChange ?? setInternalSelectedToolIds;
|
|
||||||
|
|
||||||
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
const { appDetail } = useContextSelector(AppContext, (v) => v);
|
||||||
const { llmModelList } = useSystemStore();
|
const { llmModelList } = useSystemStore();
|
||||||
@ -93,10 +93,7 @@ const GateChatInput = ({
|
|||||||
const [selectedModel, setSelectedModel] = useState(defaultModel);
|
const [selectedModel, setSelectedModel] = useState(defaultModel);
|
||||||
|
|
||||||
const showModelSelector = useMemo(() => {
|
const showModelSelector = useMemo(() => {
|
||||||
return (
|
return router.pathname === '/chat/gate';
|
||||||
router.pathname.startsWith('/chat/gate') &&
|
|
||||||
!router.pathname.includes('/chat/gate/application')
|
|
||||||
);
|
|
||||||
}, [router.pathname]);
|
}, [router.pathname]);
|
||||||
|
|
||||||
// 是否显示工具选择器
|
// 是否显示工具选择器
|
||||||
@ -188,7 +185,7 @@ const GateChatInput = ({
|
|||||||
text: textareaValue.trim(),
|
text: textareaValue.trim(),
|
||||||
files: fileList,
|
files: fileList,
|
||||||
gateModel: showModelSelector ? selectedModel : undefined,
|
gateModel: showModelSelector ? selectedModel : undefined,
|
||||||
selectedTool: selectedToolIds.length > 0 ? selectedToolIds.join(',') : null // 将工具ID数组转换为逗号分隔的字符串
|
selectedTool: selectedTools.length > 0 ? selectedTools.join(',') : null // 将工具ID数组转换为逗号分隔的字符串
|
||||||
});
|
});
|
||||||
replaceFiles([]);
|
replaceFiles([]);
|
||||||
},
|
},
|
||||||
@ -200,7 +197,7 @@ const GateChatInput = ({
|
|||||||
replaceFiles,
|
replaceFiles,
|
||||||
showModelSelector,
|
showModelSelector,
|
||||||
selectedModel,
|
selectedModel,
|
||||||
selectedToolIds
|
selectedTools
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -214,8 +211,9 @@ const GateChatInput = ({
|
|||||||
boxShadow="0px 5px 16px -4px rgba(19, 51, 107, 0.08)"
|
boxShadow="0px 5px 16px -4px rgba(19, 51, 107, 0.08)"
|
||||||
borderRadius="20px"
|
borderRadius="20px"
|
||||||
position="relative"
|
position="relative"
|
||||||
p={4}
|
px={4}
|
||||||
pb="56px"
|
pb={'62px'}
|
||||||
|
pt={fileList.length > 0 ? 0 : 4}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
transition="all 0.2s ease"
|
transition="all 0.2s ease"
|
||||||
_hover={{
|
_hover={{
|
||||||
@ -335,24 +333,18 @@ const GateChatInput = ({
|
|||||||
>
|
>
|
||||||
<Flex align="center" gap={2} overflow="hidden" maxW="65%" flexShrink={1} flexWrap="nowrap">
|
<Flex align="center" gap={2} overflow="hidden" maxW="65%" flexShrink={1} flexWrap="nowrap">
|
||||||
{showModelSelector && (
|
{showModelSelector && (
|
||||||
<GateSelect
|
<AIModelSelector
|
||||||
value={selectedModel}
|
|
||||||
list={modelList}
|
list={modelList}
|
||||||
|
value={selectedModel}
|
||||||
|
showAvatar={false}
|
||||||
onChange={setSelectedModel}
|
onChange={setSelectedModel}
|
||||||
minW="128px"
|
bg={'myGray.50'}
|
||||||
maxW="180px"
|
borderRadius={'lg'}
|
||||||
w="auto"
|
|
||||||
bg="#F9F9F9"
|
|
||||||
border="0.5px solid #E0E0E0"
|
|
||||||
borderRadius="10px"
|
|
||||||
color="#485264"
|
|
||||||
h="36px"
|
|
||||||
fontSize="14px"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showTools && (
|
{showTools && (
|
||||||
<GateToolSelect
|
<GateToolSelect
|
||||||
selectedToolIds={selectedToolIds}
|
selectedTools={selectedTools}
|
||||||
onToolsChange={setSelectedToolIds}
|
onToolsChange={setSelectedToolIds}
|
||||||
buttonSize={buttonSize}
|
buttonSize={buttonSize}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import type {
|
|||||||
} from '@fastgpt/global/core/chat/type.d';
|
} from '@fastgpt/global/core/chat/type.d';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { Box, Checkbox, Flex, Text } from '@chakra-ui/react';
|
import { Box, Checkbox, Flex, HStack, Text } from '@chakra-ui/react';
|
||||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||||
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
@ -74,6 +74,9 @@ import { useRouter } from 'next/router';
|
|||||||
import { getTeamGateConfig, getTeamGateConfigCopyRight } from '@/web/support/user/team/gate/api';
|
import { getTeamGateConfig, getTeamGateConfigCopyRight } from '@/web/support/user/team/gate/api';
|
||||||
import type { getGateConfigCopyRightResponse } from '@fastgpt/global/support/user/team/gate/api';
|
import type { getGateConfigCopyRightResponse } from '@fastgpt/global/support/user/team/gate/api';
|
||||||
import type { GateSchemaType } from '@fastgpt/global/support/user/team/gate/type';
|
import type { GateSchemaType } from '@fastgpt/global/support/user/team/gate/type';
|
||||||
|
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
|
import type { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||||
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
|
|
||||||
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
const FeedbackModal = dynamic(() => import('./components/FeedbackModal'));
|
||||||
const ReadFeedbackModal = dynamic(() => import('./components/ReadFeedbackModal'));
|
const ReadFeedbackModal = dynamic(() => import('./components/ReadFeedbackModal'));
|
||||||
@ -96,8 +99,9 @@ type Props = OutLinkChatAuthProps &
|
|||||||
showVoiceIcon?: boolean;
|
showVoiceIcon?: boolean;
|
||||||
showEmptyIntro?: boolean;
|
showEmptyIntro?: boolean;
|
||||||
active?: boolean; // can use
|
active?: boolean; // can use
|
||||||
selectedToolIds?: string[];
|
selectedTools?: FlowNodeTemplateType[];
|
||||||
onSelectedToolIdsChange?: (toolIds: string[]) => void;
|
onSelectTools?: (toolIds: FlowNodeTemplateType[]) => void;
|
||||||
|
recommendApps?: AppListItemType[];
|
||||||
|
|
||||||
onStartChat?: (e: StartChatFnProps) => Promise<
|
onStartChat?: (e: StartChatFnProps) => Promise<
|
||||||
StreamResponseType & {
|
StreamResponseType & {
|
||||||
@ -115,8 +119,9 @@ const ChatBox = ({
|
|||||||
active = true,
|
active = true,
|
||||||
onStartChat,
|
onStartChat,
|
||||||
chatType,
|
chatType,
|
||||||
selectedToolIds,
|
selectedTools,
|
||||||
onSelectedToolIdsChange
|
onSelectTools,
|
||||||
|
recommendApps = []
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const ScrollContainerRef = useRef<HTMLDivElement>(null);
|
const ScrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -421,7 +426,7 @@ const ChatBox = ({
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const inGateRoute = useMemo(() => {
|
const inGateRoute = useMemo(() => {
|
||||||
return router.pathname.startsWith('/chat/gate');
|
return router.pathname === '/chat/gate';
|
||||||
}, [router.pathname]);
|
}, [router.pathname]);
|
||||||
/**
|
/**
|
||||||
* user confirm send prompt
|
* user confirm send prompt
|
||||||
@ -1124,11 +1129,7 @@ const ChatBox = ({
|
|||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
|
|
||||||
const showWelcome = useMemo(() => {
|
const showWelcome = useMemo(() => {
|
||||||
return (
|
return router.pathname === '/chat/gate' && chatRecords.length === 0;
|
||||||
router.pathname.startsWith('/chat/gate') &&
|
|
||||||
!router.pathname.includes('/chat/gate/application') &&
|
|
||||||
chatRecords.length === 0
|
|
||||||
);
|
|
||||||
}, [router.pathname, chatRecords.length]);
|
}, [router.pathname, chatRecords.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -1169,6 +1170,29 @@ const ChatBox = ({
|
|||||||
{/* message input */}
|
{/* message input */}
|
||||||
{onStartChat && chatStarted && active && !isInteractive && (
|
{onStartChat && chatStarted && active && !isInteractive && (
|
||||||
<Box w={{ base: 'calc(100% - 48px)', md: '700px' }} maxH="132px" h="100%" px={0}>
|
<Box w={{ base: 'calc(100% - 48px)', md: '700px' }} maxH="132px" h="100%" px={0}>
|
||||||
|
<HStack mb={3}>
|
||||||
|
{recommendApps.map((item) => (
|
||||||
|
<HStack
|
||||||
|
gap={1}
|
||||||
|
key={item._id}
|
||||||
|
border={'base'}
|
||||||
|
px={2}
|
||||||
|
py={1}
|
||||||
|
borderRadius={'sm'}
|
||||||
|
cursor={'pointer'}
|
||||||
|
_hover={{
|
||||||
|
bg: 'primary.50',
|
||||||
|
borderColor: 'primary.400'
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/chat/gate/application?appId=${item._id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={item.avatar} w="1rem" h="1rem" borderRadius={'sm'} />
|
||||||
|
<Box fontSize={'sm'}>{item.name}</Box>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
<GateChatInput
|
<GateChatInput
|
||||||
onSendMessage={sendPrompt}
|
onSendMessage={sendPrompt}
|
||||||
onStop={() => chatController.current?.abort('stop')}
|
onStop={() => chatController.current?.abort('stop')}
|
||||||
@ -1176,8 +1200,8 @@ const ChatBox = ({
|
|||||||
resetInputVal={resetInputVal}
|
resetInputVal={resetInputVal}
|
||||||
chatForm={chatForm}
|
chatForm={chatForm}
|
||||||
placeholder={gateConfig?.placeholderText || '你可以问我任何问题'}
|
placeholder={gateConfig?.placeholderText || '你可以问我任何问题'}
|
||||||
selectedToolIds={selectedToolIds}
|
selectedTools={selectedTools}
|
||||||
onSelectedToolIdsChange={onSelectedToolIdsChange}
|
onSelectTools={onSelectTools}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -1205,7 +1229,7 @@ const ChatBox = ({
|
|||||||
{RenderRecords}
|
{RenderRecords}
|
||||||
|
|
||||||
{/* 移动端下的输入框和版权信息容器 */}
|
{/* 移动端下的输入框和版权信息容器 */}
|
||||||
<Flex direction="column" w="100%" mb={{ base: '12px', sm: 0 }} gap="12px">
|
<Flex direction="column" w="100%" mb={{ base: '12px', sm: 0 }}>
|
||||||
{/* message input */}
|
{/* message input */}
|
||||||
{onStartChat && chatStarted && active && !isInteractive && (
|
{onStartChat && chatStarted && active && !isInteractive && (
|
||||||
<Box
|
<Box
|
||||||
@ -1225,8 +1249,8 @@ const ChatBox = ({
|
|||||||
resetInputVal={resetInputVal}
|
resetInputVal={resetInputVal}
|
||||||
chatForm={chatForm}
|
chatForm={chatForm}
|
||||||
placeholder={gateConfig?.placeholderText || t('common:gate.placeholder')}
|
placeholder={gateConfig?.placeholderText || t('common:gate.placeholder')}
|
||||||
selectedToolIds={selectedToolIds}
|
selectedTools={selectedTools}
|
||||||
onSelectedToolIdsChange={onSelectedToolIdsChange}
|
onSelectTools={onSelectTools}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!inGateRoute && (
|
{!inGateRoute && (
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { useToast } from '@fastgpt/web/hooks/useToast';
|
|||||||
import ShareGateModal from './ShareModol';
|
import ShareGateModal from './ShareModol';
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||||
import { getMyAppsGate, postCreateApp, putAppById } from '@/web/core/app/api';
|
import { getMyAppsGate, postCreateApp, putAppById } from '@/web/core/app/api';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
|
||||||
import { emptyTemplates } from '@/web/core/app/templates';
|
import { emptyTemplates } from '@/web/core/app/templates';
|
||||||
import { saveGateConfig } from './HomeTable';
|
import { saveGateConfig } from './HomeTable';
|
||||||
import type { GateSchemaType } from '@fastgpt/global/support/user/team/gate/type';
|
import type { GateSchemaType } from '@fastgpt/global/support/user/team/gate/type';
|
||||||
@ -32,7 +31,10 @@ const ConfigButtons = ({ tab, appForm, gateConfig, copyRightConfig }: Props) =>
|
|||||||
const { runAsync: saveHomeConfig, loading: savingHome } = useRequest2(
|
const { runAsync: saveHomeConfig, loading: savingHome } = useRequest2(
|
||||||
async () => {
|
async () => {
|
||||||
if (!!gateConfig) {
|
if (!!gateConfig) {
|
||||||
await saveGateConfig(gateConfig);
|
await saveGateConfig({
|
||||||
|
...gateConfig,
|
||||||
|
status: true
|
||||||
|
});
|
||||||
toast({
|
toast({
|
||||||
title: t('common:save_success'),
|
title: t('common:save_success'),
|
||||||
status: 'success'
|
status: 'success'
|
||||||
@ -89,7 +91,6 @@ const ConfigButtons = ({ tab, appForm, gateConfig, copyRightConfig }: Props) =>
|
|||||||
const gateApp = apps.find((app) => app.type === AppTypeEnum.gate);
|
const gateApp = apps.find((app) => app.type === AppTypeEnum.gate);
|
||||||
const currentTeamAvatar = copyRightConfig?.logo;
|
const currentTeamAvatar = copyRightConfig?.logo;
|
||||||
const currentSlogan = gateConfig?.slogan;
|
const currentSlogan = gateConfig?.slogan;
|
||||||
console.log('gateApp', gateApp, currentTeamAvatar, currentSlogan, nodes, edges);
|
|
||||||
if (gateApp) {
|
if (gateApp) {
|
||||||
if (
|
if (
|
||||||
gateApp.avatar !== currentTeamAvatar ||
|
gateApp.avatar !== currentTeamAvatar ||
|
||||||
@ -104,25 +105,17 @@ const ConfigButtons = ({ tab, appForm, gateConfig, copyRightConfig }: Props) =>
|
|||||||
nodes,
|
nodes,
|
||||||
edges
|
edges
|
||||||
});
|
});
|
||||||
toast({
|
|
||||||
title: t('common:update_success'),
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await postCreateApp({
|
await postCreateApp({
|
||||||
avatar: gateConfig?.logo,
|
avatar: gateConfig?.logo,
|
||||||
name: gateConfig?.name,
|
name: 'App',
|
||||||
intro: gateConfig?.slogan,
|
intro: gateConfig?.slogan,
|
||||||
type: AppTypeEnum.gate,
|
type: AppTypeEnum.gate,
|
||||||
modules: emptyTemplates[AppTypeEnum.gate].nodes,
|
modules: emptyTemplates[AppTypeEnum.gate].nodes,
|
||||||
edges: emptyTemplates[AppTypeEnum.gate].edges,
|
edges: emptyTemplates[AppTypeEnum.gate].edges,
|
||||||
chatConfig: emptyTemplates[AppTypeEnum.gate].chatConfig
|
chatConfig: emptyTemplates[AppTypeEnum.gate].chatConfig
|
||||||
});
|
});
|
||||||
toast({
|
|
||||||
title: t('common:create_success'),
|
|
||||||
status: 'success'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import { v1Workflow2V2 } from '@/web/core/workflow/adapt';
|
|||||||
import { useMount } from 'ahooks';
|
import { useMount } from 'ahooks';
|
||||||
import type { AppDetailType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
import type { AppDetailType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import { useSimpleAppSnapshots } from '@/pageComponents/app/detail/Gate/useSnapshots';
|
import { useSimpleAppSnapshots } from '@/pageComponents/app/detail/Gate/useSnapshots';
|
||||||
import { Dropdown } from 'react-day-picker';
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import AddQuickAppModal from './AddQuickAppModal';
|
import AddQuickAppModal from './AddQuickAppModal';
|
||||||
import { listQuickApps } from '@/web/support/user/team/gate/quickApp';
|
import { listQuickApps } from '@/web/support/user/team/gate/quickApp';
|
||||||
@ -43,7 +42,6 @@ type Props = {
|
|||||||
tools: string[];
|
tools: string[];
|
||||||
slogan: string;
|
slogan: string;
|
||||||
placeholderText: string;
|
placeholderText: string;
|
||||||
onStatusChange?: (status: boolean) => void;
|
|
||||||
onSloganChange?: (slogan: string) => void;
|
onSloganChange?: (slogan: string) => void;
|
||||||
onPlaceholderChange?: (text: string) => void;
|
onPlaceholderChange?: (text: string) => void;
|
||||||
onToolsChange?: (tools: string[]) => void;
|
onToolsChange?: (tools: string[]) => void;
|
||||||
@ -54,7 +52,6 @@ const HomeTable = ({
|
|||||||
appDetail,
|
appDetail,
|
||||||
slogan,
|
slogan,
|
||||||
placeholderText,
|
placeholderText,
|
||||||
onStatusChange,
|
|
||||||
onSloganChange,
|
onSloganChange,
|
||||||
onPlaceholderChange,
|
onPlaceholderChange,
|
||||||
onAppFormChange
|
onAppFormChange
|
||||||
@ -165,12 +162,6 @@ const HomeTable = ({
|
|||||||
letterSpacing: '0.25px'
|
letterSpacing: '0.25px'
|
||||||
};
|
};
|
||||||
|
|
||||||
// 响应式工具布局
|
|
||||||
|
|
||||||
const handleStatusChange = (val: string) => {
|
|
||||||
onStatusChange?.(val === 'enabled');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSloganChange = (val: string) => {
|
const handleSloganChange = (val: string) => {
|
||||||
onSloganChange?.(val);
|
onSloganChange?.(val);
|
||||||
};
|
};
|
||||||
@ -287,75 +278,6 @@ const HomeTable = ({
|
|||||||
pb={6}
|
pb={6}
|
||||||
pt={{ base: 4, md: 6 }}
|
pt={{ base: 4, md: 6 }}
|
||||||
>
|
>
|
||||||
{/* 状态选择 */}
|
|
||||||
<FormControl display="flex" flexDirection="column" gap={spacing.sm} w="full">
|
|
||||||
<FormLabel
|
|
||||||
fontWeight={formStyles.fontWeight}
|
|
||||||
fontSize={formStyles.fontSize}
|
|
||||||
lineHeight={formStyles.lineHeight}
|
|
||||||
letterSpacing={formStyles.letterSpacing}
|
|
||||||
color="myGray.700"
|
|
||||||
mb="0"
|
|
||||||
>
|
|
||||||
{t('account_gate:status')}
|
|
||||||
</FormLabel>
|
|
||||||
<RadioGroup value={status ? 'enabled' : 'disabled'} onChange={handleStatusChange}>
|
|
||||||
<Stack direction={{ base: 'column', sm: 'row' }} spacing={spacing.md}>
|
|
||||||
<Flex
|
|
||||||
alignItems="center"
|
|
||||||
p={`${spacing.sm} ${spacing.lg} ${spacing.sm} ${spacing.md}`}
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={status ? 'primary.500' : 'myGray.200'}
|
|
||||||
borderRadius="7px"
|
|
||||||
bg={status ? 'blue.50' : 'white'}
|
|
||||||
transition="all 0.2s ease-in-out"
|
|
||||||
_hover={{
|
|
||||||
bg: status ? 'blue.100' : 'myGray.50',
|
|
||||||
borderColor: status ? 'primary.600' : 'myGray.300',
|
|
||||||
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
|
|
||||||
transform: 'translateY(-1px)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Radio value="enabled" colorScheme="blue">
|
|
||||||
<Text
|
|
||||||
fontSize={formStyles.fontSize}
|
|
||||||
lineHeight={formStyles.lineHeight}
|
|
||||||
fontWeight={formStyles.fontWeight}
|
|
||||||
letterSpacing={formStyles.letterSpacing}
|
|
||||||
>
|
|
||||||
{t('account_gate:enabled')}
|
|
||||||
</Text>
|
|
||||||
</Radio>
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
alignItems="center"
|
|
||||||
p={`${spacing.sm} ${spacing.lg} ${spacing.sm} ${spacing.md}`}
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={!status ? 'primary.500' : 'myGray.200'}
|
|
||||||
borderRadius="7px"
|
|
||||||
bg={!status ? 'blue.50' : 'white'}
|
|
||||||
transition="all 0.2s ease-in-out"
|
|
||||||
_hover={{
|
|
||||||
bg: !status ? 'blue.100' : 'myGray.50',
|
|
||||||
borderColor: !status ? 'primary.600' : 'myGray.300',
|
|
||||||
boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.05)',
|
|
||||||
transform: 'translateY(-1px)'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Radio value="disabled" colorScheme="blue">
|
|
||||||
<Text
|
|
||||||
fontSize={formStyles.fontSize}
|
|
||||||
lineHeight={formStyles.lineHeight}
|
|
||||||
fontWeight={formStyles.fontWeight}
|
|
||||||
letterSpacing={formStyles.letterSpacing}
|
|
||||||
>
|
|
||||||
{t('account_gate:disabled')}
|
|
||||||
</Text>
|
|
||||||
</Radio>
|
|
||||||
</Flex>
|
|
||||||
</Stack>
|
|
||||||
</RadioGroup>
|
|
||||||
</FormControl>
|
|
||||||
{/* 快捷应用 */}
|
{/* 快捷应用 */}
|
||||||
<FormControl
|
<FormControl
|
||||||
display={'flex'}
|
display={'flex'}
|
||||||
@ -365,7 +287,7 @@ const HomeTable = ({
|
|||||||
gap={'8px'}
|
gap={'8px'}
|
||||||
>
|
>
|
||||||
{/* 标题行 */}
|
{/* 标题行 */}
|
||||||
<Flex alignItems={'center'} gap={'4px'}>
|
<Flex alignItems={'center'} gap={'1'}>
|
||||||
<Text
|
<Text
|
||||||
color={'var(--Gray-Modern-600, #485264)'}
|
color={'var(--Gray-Modern-600, #485264)'}
|
||||||
fontFamily={'PingFang SC'}
|
fontFamily={'PingFang SC'}
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { Box, Button, Flex, Grid, useDisclosure, Text } from '@chakra-ui/react';
|
import { Box, Button, Flex, Grid, useDisclosure, Text } from '@chakra-ui/react';
|
||||||
import React, { useMemo, useState, useCallback, useEffect, useRef } from 'react';
|
import React, { useState, useCallback } from 'react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||||
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
import type { AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
import { theme } from '@fastgpt/web/styles/theme';
|
|
||||||
import {
|
import {
|
||||||
FlowNodeInputTypeEnum,
|
FlowNodeInputTypeEnum,
|
||||||
FlowNodeTypeEnum
|
FlowNodeTypeEnum
|
||||||
@ -113,9 +112,8 @@ const ToolSelect = ({
|
|||||||
<>
|
<>
|
||||||
{/* 标题区域 */}
|
{/* 标题区域 */}
|
||||||
<Flex alignItems="center" justifyContent="space-between" width="100%">
|
<Flex alignItems="center" justifyContent="space-between" width="100%">
|
||||||
<Flex alignItems="center" gap={spacing.xs}>
|
<Flex alignItems="center" gap={1}>
|
||||||
<Text
|
<Text
|
||||||
ml={2}
|
|
||||||
fontWeight={formStyles.fontWeight}
|
fontWeight={formStyles.fontWeight}
|
||||||
fontSize={formStyles.fontSize}
|
fontSize={formStyles.fontSize}
|
||||||
lineHeight={formStyles.lineHeight}
|
lineHeight={formStyles.lineHeight}
|
||||||
@ -124,7 +122,7 @@ const ToolSelect = ({
|
|||||||
>
|
>
|
||||||
{t('common:core.app.Tool call')}
|
{t('common:core.app.Tool call')}
|
||||||
</Text>
|
</Text>
|
||||||
<QuestionTip ml={1} label={t('app:plugin_dispatch_tip')} />
|
<QuestionTip label={t('app:plugin_dispatch_tip')} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 已有工具时显示新增按钮 */}
|
{/* 已有工具时显示新增按钮 */}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
import { Box, Flex, HStack } from '@chakra-ui/react';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { useSafeState } from 'ahooks';
|
|
||||||
import type { AppDetailType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
import type { AppDetailType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { useChatGate } from '../useChatGate';
|
import { useChatGate } from '../useChatGate';
|
||||||
@ -11,6 +10,9 @@ import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
|||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import { cardStyles } from '../constants';
|
import { cardStyles } from '../constants';
|
||||||
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
|
import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList';
|
||||||
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
|
import { getQuickApps, listQuickApps } from '@/web/support/user/team/gate/quickApp';
|
||||||
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appForm: AppSimpleEditFormType;
|
appForm: AppSimpleEditFormType;
|
||||||
@ -18,31 +20,17 @@ type Props = {
|
|||||||
appDetail: AppDetailType; // 添加 appDetail prop
|
appDetail: AppDetailType; // 添加 appDetail prop
|
||||||
};
|
};
|
||||||
const ChatGate = ({ appForm, setRenderEdit, appDetail }: Props) => {
|
const ChatGate = ({ appForm, setRenderEdit, appDetail }: Props) => {
|
||||||
console.log('appDetai', appDetail);
|
|
||||||
console.log('appform', appForm);
|
|
||||||
|
|
||||||
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
||||||
const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
|
const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
|
||||||
|
|
||||||
// 添加 selectedToolIds 状态管理
|
|
||||||
const [selectedToolIds, setSelectedToolIds] = useState<string[]>([]);
|
|
||||||
|
|
||||||
const [workflowData] = useSafeState({
|
|
||||||
nodes: appDetail.modules || [],
|
|
||||||
edges: appDetail.edges || []
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRenderEdit(!datasetCiteData);
|
setRenderEdit(!datasetCiteData);
|
||||||
}, [datasetCiteData, setRenderEdit]);
|
}, [datasetCiteData, setRenderEdit]);
|
||||||
|
|
||||||
const { ChatContainer, restartChat, loading } = useChatGate({
|
const { ChatContainer } = useChatGate({
|
||||||
...workflowData,
|
appForm,
|
||||||
chatConfig: appForm.chatConfig,
|
|
||||||
isReady: true,
|
isReady: true,
|
||||||
appDetail,
|
appDetail
|
||||||
selectedToolIds, // 传递 selectedToolIds
|
|
||||||
onSelectedToolIdsChange: setSelectedToolIds // 传递更新函数
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -12,25 +12,31 @@ import {
|
|||||||
ModalHeader,
|
ModalHeader,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
ModalCloseButton,
|
ModalCloseButton,
|
||||||
useDisclosure
|
useDisclosure,
|
||||||
|
HStack
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { getTeamGateConfig } from '@/web/support/user/team/gate/api';
|
import { getTeamGateConfig } from '@/web/support/user/team/gate/api';
|
||||||
import { getSystemPlugTemplates, getTeamPlugTemplates } from '@/web/core/app/api/plugin';
|
import {
|
||||||
import type { NodeTemplateListItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
getPreviewPluginNode,
|
||||||
|
getSystemPlugTemplates,
|
||||||
|
getTeamPlugTemplates
|
||||||
|
} from '@/web/core/app/api/plugin';
|
||||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||||
|
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
|
|
||||||
type GateToolSelectProps = {
|
type GateToolSelectProps = {
|
||||||
selectedToolIds: string[];
|
selectedTools: FlowNodeTemplateType[];
|
||||||
onToolsChange: (toolIds: string[]) => void;
|
onToolsChange: (tools: FlowNodeTemplateType[]) => void;
|
||||||
buttonSize?: string;
|
buttonSize?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GateToolSelect = ({
|
const GateToolSelect = ({
|
||||||
selectedToolIds,
|
selectedTools,
|
||||||
onToolsChange,
|
onToolsChange,
|
||||||
buttonSize = 'md'
|
buttonSize = 'md'
|
||||||
}: GateToolSelectProps) => {
|
}: GateToolSelectProps) => {
|
||||||
@ -77,60 +83,83 @@ const GateToolSelect = ({
|
|||||||
}, [gateConfig?.tools, allAvailableTools]);
|
}, [gateConfig?.tools, allAvailableTools]);
|
||||||
|
|
||||||
// 处理单个工具的选择/取消选择
|
// 处理单个工具的选择/取消选择
|
||||||
const handleToolSelect = useCallback(
|
const { runAsync: handleToolSelect, loading: loadingToolSelect } = useRequest2(
|
||||||
(toolId: string, checked: boolean) => {
|
async (toolId: string, checked: boolean) => {
|
||||||
const newSelectedIds = checked
|
const tool = allAvailableTools.find((item) => item.id === toolId);
|
||||||
? [...selectedToolIds, toolId]
|
if (!tool) return;
|
||||||
: selectedToolIds.filter((id) => id !== toolId);
|
|
||||||
onToolsChange(newSelectedIds);
|
if (checked) {
|
||||||
|
const res = await getPreviewPluginNode({ appId: tool.id });
|
||||||
|
onToolsChange([...selectedTools, res]);
|
||||||
|
} else {
|
||||||
|
onToolsChange(selectedTools.filter((item) => item.pluginId !== toolId));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[selectedToolIds, onToolsChange]
|
{
|
||||||
|
refreshDeps: [allAvailableTools, selectedTools, onToolsChange]
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedCount = selectedToolIds.length;
|
const selectedCount = selectedTools.length;
|
||||||
const loading = loadingGateConfig || loadingSystemPlugins || loadingTeamPlugins;
|
const loading = loadingGateConfig || loadingSystemPlugins || loadingTeamPlugins;
|
||||||
|
|
||||||
// 调试信息
|
return availableTools.length > 0 ? (
|
||||||
console.log('GateToolSelect Debug:', {
|
|
||||||
isOpen,
|
|
||||||
loading,
|
|
||||||
availableTools: availableTools.length,
|
|
||||||
gateConfigTools: gateConfig?.tools?.length || 0,
|
|
||||||
systemPlugins: systemPlugins.length,
|
|
||||||
teamPlugins: teamPlugins.length,
|
|
||||||
allAvailableTools: allAvailableTools.length
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
<>
|
||||||
<Button
|
{availableTools.length > 1 ? (
|
||||||
leftIcon={
|
<Button
|
||||||
<MyIcon name={'support/gate/chat/toolkitLine'} w={'18px'} h={'18px'} color="blue.500" />
|
leftIcon={
|
||||||
}
|
<MyIcon name={'support/gate/chat/toolkitLine'} w={'18px'} h={'18px'} color="blue.500" />
|
||||||
size={buttonSize}
|
}
|
||||||
display="flex"
|
size={buttonSize}
|
||||||
padding="8px 12px"
|
display="flex"
|
||||||
justifyContent="center"
|
padding="8px 12px"
|
||||||
alignItems="center"
|
justifyContent="center"
|
||||||
gap="4px"
|
alignItems="center"
|
||||||
iconSpacing="4px"
|
gap="4px"
|
||||||
borderRadius="9999px"
|
iconSpacing="4px"
|
||||||
border="0.5px solid var(--Royal-Blue-200, #C5D7FF)"
|
borderRadius="9999px"
|
||||||
background="var(--light-fastgpt-primary-container-low, #F0F4FF)"
|
border="0.5px solid var(--Royal-Blue-200, #C5D7FF)"
|
||||||
color="blue.500"
|
background="var(--light-fastgpt-primary-container-low, #F0F4FF)"
|
||||||
fontWeight="500"
|
color="blue.500"
|
||||||
onClick={() => {
|
fontWeight="500"
|
||||||
console.log('Button clicked, opening modal');
|
onClick={onOpen}
|
||||||
onOpen();
|
flexShrink={0}
|
||||||
}}
|
_hover={{
|
||||||
flexShrink={0}
|
background: 'var(--light-fastgpt-primary-container-low, #E6EDFF)'
|
||||||
_hover={{
|
}}
|
||||||
background: 'var(--light-fastgpt-primary-container-low, #E6EDFF)'
|
>
|
||||||
}}
|
<Box display={{ base: 'none', md: 'block' }}>{t('common:tool_select')}: </Box>
|
||||||
>
|
{selectedCount}
|
||||||
<Box display={{ base: 'none', md: 'block' }}>{t('common:tool_select')}: </Box>
|
</Button>
|
||||||
{selectedCount}
|
) : (
|
||||||
</Button>
|
<MyBox
|
||||||
|
isLoading={loadingToolSelect}
|
||||||
|
display={'flex'}
|
||||||
|
alignItems={'center'}
|
||||||
|
gap={2}
|
||||||
|
h={'40px'}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
border={'base'}
|
||||||
|
px={3}
|
||||||
|
cursor={'pointer'}
|
||||||
|
userSelect={'none'}
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'primary.300'
|
||||||
|
}}
|
||||||
|
{...(selectedTools.length > 0 && {
|
||||||
|
borderColor: 'primary.400 !important',
|
||||||
|
bg: 'primary.50'
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
handleToolSelect(availableTools[0].id, selectedTools.length === 0);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar src={availableTools[0].avatar} w="1.2rem" h="1.2rem" borderRadius={'sm'} />
|
||||||
|
<Text display={['none', 'none', 'block']} fontSize="md" fontWeight="medium">
|
||||||
|
{availableTools[0].name}
|
||||||
|
</Text>
|
||||||
|
</MyBox>
|
||||||
|
)}
|
||||||
|
|
||||||
<Modal isOpen={isOpen} onClose={onClose} size="md">
|
<Modal isOpen={isOpen} onClose={onClose} size="md">
|
||||||
<ModalOverlay />
|
<ModalOverlay />
|
||||||
@ -152,76 +181,83 @@ const GateToolSelect = ({
|
|||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
|
|
||||||
<ModalBody pb={6}>
|
<ModalBody pb={6}>
|
||||||
{loading ? (
|
<MyBox isLoading={loadingToolSelect}>
|
||||||
<Flex justify="center" py={8}>
|
{loading ? (
|
||||||
<Text fontSize="sm" color="myGray.500">
|
<Flex justify="center" py={8}>
|
||||||
{t('common:Loading')}
|
<Text fontSize="sm" color="myGray.500">
|
||||||
</Text>
|
{t('common:Loading')}
|
||||||
</Flex>
|
</Text>
|
||||||
) : availableTools.length === 0 ? (
|
</Flex>
|
||||||
<Box py={8} textAlign="center">
|
) : availableTools.length === 0 ? (
|
||||||
<EmptyTip text="暂无可用工具" />
|
<Box py={8} textAlign="center">
|
||||||
<Text fontSize="sm" color="myGray.500" mt={3}>
|
<EmptyTip text="暂无可用工具" />
|
||||||
请先在门户管理中配置工具,或检查是否有可用的插件
|
<Text fontSize="sm" color="myGray.500" mt={3}>
|
||||||
</Text>
|
请先在门户管理中配置工具,或检查是否有可用的插件
|
||||||
</Box>
|
</Text>
|
||||||
) : (
|
</Box>
|
||||||
<VStack align="stretch" spacing={2}>
|
) : (
|
||||||
{availableTools.map((tool) => (
|
<VStack align="stretch" spacing={2}>
|
||||||
<Box
|
{availableTools.map((tool) => (
|
||||||
key={tool.id}
|
<Box
|
||||||
p={4}
|
key={tool.id}
|
||||||
borderRadius="md"
|
p={4}
|
||||||
cursor="pointer"
|
borderRadius="md"
|
||||||
border="1px solid"
|
cursor="pointer"
|
||||||
borderColor="gray.200"
|
border="1px solid"
|
||||||
transition="all 0.2s"
|
borderColor="gray.200"
|
||||||
_hover={{
|
transition="all 0.2s"
|
||||||
bg: 'blue.50',
|
_hover={{
|
||||||
borderColor: 'blue.300'
|
bg: 'blue.50',
|
||||||
}}
|
borderColor: 'blue.300'
|
||||||
onClick={() => handleToolSelect(tool.id, !selectedToolIds.includes(tool.id))}
|
}}
|
||||||
>
|
onClick={() =>
|
||||||
<Flex align="center">
|
handleToolSelect(
|
||||||
<Checkbox
|
tool.id,
|
||||||
size="md"
|
!selectedTools.some((item) => item.pluginId === tool.id)
|
||||||
isChecked={selectedToolIds.includes(tool.id)}
|
)
|
||||||
onChange={(e) => {
|
}
|
||||||
e.stopPropagation();
|
>
|
||||||
handleToolSelect(tool.id, e.target.checked);
|
<Flex align="center">
|
||||||
}}
|
<Checkbox
|
||||||
mr={4}
|
size="md"
|
||||||
colorScheme="blue"
|
isChecked={selectedTools.some((item) => item.pluginId === tool.id)}
|
||||||
/>
|
onChange={(e) => {
|
||||||
<Avatar src={tool.avatar} w="32px" h="32px" mr={3} />
|
e.stopPropagation();
|
||||||
<Box flex={1}>
|
handleToolSelect(tool.id, e.target.checked);
|
||||||
<Text fontSize="md" fontWeight="medium" color="myGray.900">
|
}}
|
||||||
{tool.name}
|
mr={4}
|
||||||
</Text>
|
colorScheme="blue"
|
||||||
{tool.intro && (
|
/>
|
||||||
<Text fontSize="sm" color="myGray.600" mt={1} noOfLines={2}>
|
<Avatar src={tool.avatar} w="32px" h="32px" mr={3} />
|
||||||
{tool.intro}
|
<Box flex={1}>
|
||||||
|
<Text fontSize="md" fontWeight="medium" color="myGray.900">
|
||||||
|
{tool.name}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
{tool.intro && (
|
||||||
</Box>
|
<Text fontSize="sm" color="myGray.600" mt={1} noOfLines={2}>
|
||||||
</Flex>
|
{tool.intro}
|
||||||
</Box>
|
</Text>
|
||||||
))}
|
)}
|
||||||
</VStack>
|
</Box>
|
||||||
)}
|
</Flex>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
)}
|
||||||
|
|
||||||
{selectedToolIds.length > 0 && (
|
{selectedTools.length > 0 && (
|
||||||
<Box mt={4} p={3} bg="blue.50" borderRadius="md">
|
<Box mt={4} p={3} bg="blue.50" borderRadius="md">
|
||||||
<Text fontSize="sm" color="blue.700">
|
<Text fontSize="sm" color="blue.700">
|
||||||
已选择 {selectedToolIds.length} 个工具
|
已选择 {selectedTools.length} 个工具
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
</MyBox>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
);
|
) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(GateToolSelect);
|
export default React.memo(GateToolSelect);
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||||
import { streamFetch } from '@/web/common/api/fetch';
|
import { streamFetch } from '@/web/common/api/fetch';
|
||||||
import { useMemoizedFn } from 'ahooks';
|
import { useMemoizedFn, useSafeState } from 'ahooks';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import type { AppDetailType, AppSimpleEditFormType } from '@fastgpt/global/core/app/type';
|
||||||
import type { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
|
|
||||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
|
||||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { Box } from '@chakra-ui/react';
|
|
||||||
import type { AppChatConfigType, AppDetailType } from '@fastgpt/global/core/app/type';
|
|
||||||
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
|
||||||
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
import { useChatStore } from '@/web/core/chat/context/useChatStore';
|
||||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||||
@ -20,29 +14,39 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||||
|
import { form2AppWorkflow } from '@/web/core/app/utils';
|
||||||
const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox'));
|
import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
|
import { listQuickApps } from '@/web/support/user/team/gate/quickApp';
|
||||||
|
|
||||||
export const useChatGate = ({
|
export const useChatGate = ({
|
||||||
selectedToolIds,
|
appForm,
|
||||||
onSelectedToolIdsChange,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
chatConfig,
|
|
||||||
isReady,
|
isReady,
|
||||||
appDetail
|
appDetail
|
||||||
}: {
|
}: {
|
||||||
selectedToolIds?: string[];
|
appForm: AppSimpleEditFormType;
|
||||||
onSelectedToolIdsChange?: (toolIds: string[]) => void;
|
|
||||||
nodes: StoreNodeItemType[];
|
|
||||||
edges: StoreEdgeItemType[];
|
|
||||||
chatConfig: AppChatConfigType;
|
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
appDetail: AppDetailType;
|
appDetail: AppDetailType;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { userInfo } = useUserStore();
|
const { userInfo } = useUserStore();
|
||||||
const { setChatId, chatId, appId } = useChatStore();
|
const { setChatId, chatId, appId } = useChatStore();
|
||||||
|
const [selectedTools, setSelectedTools] = useState<FlowNodeTemplateType[]>([]);
|
||||||
|
|
||||||
|
const [workflowData, setWorkflowData] = useSafeState({
|
||||||
|
nodes: appDetail.modules || [],
|
||||||
|
edges: appDetail.edges || []
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const { nodes, edges } = form2AppWorkflow(
|
||||||
|
{
|
||||||
|
...appForm,
|
||||||
|
selectedTools
|
||||||
|
},
|
||||||
|
t
|
||||||
|
);
|
||||||
|
setWorkflowData({ nodes, edges });
|
||||||
|
}, [appForm, selectedTools, setWorkflowData, t]);
|
||||||
|
|
||||||
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
|
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
|
||||||
|
|
||||||
const startChat = useMemoizedFn(
|
const startChat = useMemoizedFn(
|
||||||
@ -61,19 +65,18 @@ export const useChatGate = ({
|
|||||||
data: {
|
data: {
|
||||||
// Send histories and user messages
|
// Send histories and user messages
|
||||||
messages: histories,
|
messages: histories,
|
||||||
nodes,
|
nodes: workflowData.nodes,
|
||||||
edges,
|
edges: workflowData.edges,
|
||||||
variables,
|
variables,
|
||||||
responseChatItemId,
|
responseChatItemId,
|
||||||
appId,
|
appId,
|
||||||
appName: t('chat:chat_gate_app', { name: appDetail.name }),
|
appName: t('chat:chat_gate_app', { name: appDetail.name }),
|
||||||
chatId,
|
chatId,
|
||||||
chatConfig,
|
chatConfig: appForm.chatConfig,
|
||||||
metadata: {
|
metadata: {
|
||||||
source: 'web',
|
source: 'web',
|
||||||
userAgent: navigator.userAgent
|
userAgent: navigator.userAgent
|
||||||
},
|
}
|
||||||
selectedToolIds: selectedToolIds || []
|
|
||||||
},
|
},
|
||||||
onMessage: generatingMessage,
|
onMessage: generatingMessage,
|
||||||
abortCtrl: controller
|
abortCtrl: controller
|
||||||
@ -99,22 +102,18 @@ export const useChatGate = ({
|
|||||||
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
|
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
|
||||||
const clearChatRecords = useContextSelector(ChatItemContext, (v) => v.clearChatRecords);
|
const clearChatRecords = useContextSelector(ChatItemContext, (v) => v.clearChatRecords);
|
||||||
|
|
||||||
const pluginInputs = useMemo(() => {
|
|
||||||
return nodes.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs || [];
|
|
||||||
}, [nodes]);
|
|
||||||
|
|
||||||
// Set chat box data
|
// Set chat box data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setChatBoxData({
|
setChatBoxData({
|
||||||
userAvatar: userInfo?.avatar,
|
userAvatar: userInfo?.avatar,
|
||||||
appId: appId,
|
appId: appId,
|
||||||
app: {
|
app: {
|
||||||
chatConfig,
|
chatConfig: appForm.chatConfig,
|
||||||
name: appDetail.name,
|
name: appDetail.name,
|
||||||
avatar: appDetail.avatar,
|
avatar: appDetail.avatar,
|
||||||
intro: appDetail.intro,
|
intro: appDetail.intro,
|
||||||
type: appDetail.type,
|
type: appDetail.type,
|
||||||
pluginInputs
|
pluginInputs: []
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
@ -122,9 +121,8 @@ export const useChatGate = ({
|
|||||||
appDetail.intro,
|
appDetail.intro,
|
||||||
appDetail.name,
|
appDetail.name,
|
||||||
appDetail.type,
|
appDetail.type,
|
||||||
|
appForm.chatConfig,
|
||||||
appId,
|
appId,
|
||||||
chatConfig,
|
|
||||||
pluginInputs,
|
|
||||||
setChatBoxData,
|
setChatBoxData,
|
||||||
userInfo?.avatar
|
userInfo?.avatar
|
||||||
]);
|
]);
|
||||||
@ -151,29 +149,24 @@ export const useChatGate = ({
|
|||||||
setChatId();
|
setChatId();
|
||||||
}, [clearChatRecords, setChatId]);
|
}, [clearChatRecords, setChatId]);
|
||||||
|
|
||||||
const CustomChatContainer = useMemoizedFn(() =>
|
// 精选应用
|
||||||
appDetail.type === AppTypeEnum.plugin ? (
|
const { data: recommendApps = [] } = useRequest2(listQuickApps, {
|
||||||
<Box p={5} pb={16}>
|
manual: false
|
||||||
<PluginRunBox
|
});
|
||||||
appId={appId}
|
|
||||||
chatId={chatId}
|
const CustomChatContainer = useMemoizedFn(() => (
|
||||||
onNewChat={restartChat}
|
<ChatBox
|
||||||
onStartChat={startChat}
|
isReady={isReady}
|
||||||
/>
|
appId={appId}
|
||||||
</Box>
|
chatId={chatId}
|
||||||
) : (
|
showMarkIcon
|
||||||
<ChatBox
|
chatType={'chat'}
|
||||||
isReady={isReady}
|
onStartChat={startChat}
|
||||||
appId={appId}
|
selectedTools={selectedTools}
|
||||||
chatId={chatId}
|
onSelectTools={setSelectedTools}
|
||||||
showMarkIcon
|
recommendApps={recommendApps}
|
||||||
chatType={'chat'}
|
/>
|
||||||
onStartChat={startChat}
|
));
|
||||||
selectedToolIds={selectedToolIds}
|
|
||||||
onSelectedToolIdsChange={onSelectedToolIdsChange}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ChatContainer: CustomChatContainer,
|
ChatContainer: CustomChatContainer,
|
||||||
|
|||||||
@ -29,9 +29,9 @@ type TabType = 'home' | 'copyright' | 'app' | 'logs';
|
|||||||
|
|
||||||
const GatewayConfig = () => {
|
const GatewayConfig = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [gateConfig, setGateConfig] = useState<GateSchemaType | undefined>(undefined);
|
const [gateConfig, setGateConfig] = useState<GateSchemaType>();
|
||||||
// 添加 appForm 状态
|
// 添加 appForm 状态
|
||||||
const [appForm, setAppForm] = useState<AppSimpleEditFormType | undefined>(undefined);
|
const [appForm, setAppForm] = useState<AppSimpleEditFormType>();
|
||||||
//从 appForm 中获取 selectedTools的 id 组成 string 数组
|
//从 appForm 中获取 selectedTools的 id 组成 string 数组
|
||||||
|
|
||||||
//gateConfig?.tools 改成
|
//gateConfig?.tools 改成
|
||||||
|
|||||||
@ -64,7 +64,6 @@ export type Props = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
chatConfig: AppChatConfigType;
|
chatConfig: AppChatConfigType;
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
selectedToolIds?: string[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@ -81,8 +80,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
appId,
|
appId,
|
||||||
chatConfig,
|
chatConfig,
|
||||||
chatId,
|
chatId,
|
||||||
metadata = {},
|
metadata = {}
|
||||||
selectedToolIds = []
|
|
||||||
} = req.body as Props;
|
} = req.body as Props;
|
||||||
try {
|
try {
|
||||||
if (!Array.isArray(nodes)) {
|
if (!Array.isArray(nodes)) {
|
||||||
@ -91,47 +89,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (!Array.isArray(edges)) {
|
if (!Array.isArray(edges)) {
|
||||||
throw new Error('Edges is not array');
|
throw new Error('Edges is not array');
|
||||||
}
|
}
|
||||||
|
|
||||||
//对边进行过滤,只保留selectedToolIds中的边
|
|
||||||
console.log('selectedToolIds', selectedToolIds);
|
|
||||||
|
|
||||||
// 创建从 pluginId 到 nodeId 的映射
|
|
||||||
const pluginIdToNodeIdMap = new Map<string, string>();
|
|
||||||
nodes.forEach((node) => {
|
|
||||||
if (node.pluginId) {
|
|
||||||
pluginIdToNodeIdMap.set(node.pluginId, node.nodeId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('pluginIdToNodeIdMap', Object.fromEntries(pluginIdToNodeIdMap));
|
|
||||||
|
|
||||||
// 获取选中工具对应的 nodeId 集合
|
|
||||||
const selectedNodeIds = new Set<string>();
|
|
||||||
selectedToolIds.forEach((pluginId) => {
|
|
||||||
const nodeId = pluginIdToNodeIdMap.get(pluginId);
|
|
||||||
if (nodeId) {
|
|
||||||
selectedNodeIds.add(nodeId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('selectedNodeIds', Array.from(selectedNodeIds));
|
|
||||||
|
|
||||||
// 过滤边:保留第一个边和目标节点在选中工具中的边
|
|
||||||
const filteredEdges = edges.filter((edge, index) => {
|
|
||||||
// 保留第一个边
|
|
||||||
if (index === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保留目标节点在选中工具中的边
|
|
||||||
return selectedNodeIds.has(edge.target);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Original edges count:', edges.length);
|
|
||||||
console.log('Filtered edges count:', filteredEdges.length);
|
|
||||||
console.log('Filtered edges:', filteredEdges);
|
|
||||||
|
|
||||||
// 使用过滤后的边
|
|
||||||
edges = filteredEdges;
|
|
||||||
|
|
||||||
const chatMessages = GPTMessages2Chats(messages);
|
const chatMessages = GPTMessages2Chats(messages);
|
||||||
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
|
// console.log(JSON.stringify(chatMessages, null, 2), '====', chatMessages.length);
|
||||||
|
|
||||||
|
|||||||
@ -10,9 +10,6 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import FoldButton from '@/pageComponents/chat/gatechat/FoldButton';
|
import FoldButton from '@/pageComponents/chat/gatechat/FoldButton';
|
||||||
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
|
||||||
import PageContainer from '@/components/PageContainer';
|
import PageContainer from '@/components/PageContainer';
|
||||||
import SideBar from '@/components/SideBar';
|
|
||||||
import ChatHistorySlider from '@/pageComponents/chat/ChatHistorySlider';
|
|
||||||
import SliderApps from '@/pageComponents/chat/SliderApps';
|
|
||||||
import ChatHeader from '@/pageComponents/chat/ChatHeader';
|
import ChatHeader from '@/pageComponents/chat/ChatHeader';
|
||||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||||
import { serviceSideProps } from '@/web/common/i18n/utils';
|
import { serviceSideProps } from '@/web/common/i18n/utils';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user