4.8.5 test (#1819)

This commit is contained in:
Archer
2024-06-21 18:32:05 +08:00
committed by GitHub
parent 5cc01b8509
commit 24596a6e21
40 changed files with 908 additions and 1058 deletions

View File

@@ -10,6 +10,7 @@ jobs:
build-fastgpt-sandbox-images:
runs-on: ubuntu-20.04
steps:
# install env
- name: Checkout
uses: actions/checkout@v3
with:
@@ -31,6 +32,7 @@ jobs:
restore-keys: |
${{ runner.os }}-buildx-
# login docker
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
@@ -49,6 +51,7 @@ jobs:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# Set tag
- name: Set image name and tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
@@ -66,6 +69,7 @@ jobs:
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
Git_Tag: ${{ env.Git_Tag }}

View File

@@ -11,6 +11,7 @@ jobs:
build-fastgpt-images:
runs-on: ubuntu-20.04
steps:
# install env
- name: Checkout
uses: actions/checkout@v3
with:
@@ -31,19 +32,45 @@ jobs:
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
# login docker
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
- name: Login to Ali Hub
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_HUB_USERNAME }}
password: ${{ secrets.ALI_HUB_PASSWORD }}
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
# Set tag
- name: Set image name and tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
else
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
fi
- name: Build and publish image for main branch or tag push event
env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
@@ -56,56 +83,10 @@ jobs:
--push \
--cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/tmp/.buildx-cache \
-t ${DOCKER_REPO_TAGGED} \
-t ${Git_Tag} \
-t ${Git_Latest} \
-t ${Ali_Tag} \
-t ${Ali_Latest} \
-t ${Docker_Hub_Tag} \
-t ${Docker_Hub_Latest} \
.
push-to-docker-hub:
needs: build-fastgpt-images
runs-on: ubuntu-20.04
if: github.repository == 'labring/FastGPT'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_NAME }}
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
push-to-ali-hub:
needs: build-fastgpt-images
if: github.repository == 'labring/FastGPT'
runs-on: ubuntu-20.04
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Login to Ali Hub
uses: docker/login-action@v2
with:
registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALI_HUB_USERNAME }}
password: ${{ secrets.ALI_HUB_PASSWORD }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag
run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
else
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
fi
- name: Pull image from GitHub Container Registry
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
- name: Tag image with Docker Hub repository name and version tag
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
- name: Push image to Docker Hub
run: docker push ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}

View File

@@ -31,7 +31,9 @@ images: []
### 页面崩溃
1. 关闭翻译
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。95%情况是配置文件不对可以F12打开控制台看具体的空指针情况
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。
* 95%情况是配置文件不对。会提示 xxx undefined
* 提示`URI malformed`,请 Issue 反馈具体操作和页面,这是由于特殊字符串编码解析报错。
3. 某些api不兼容问题较少
### 开启内容补全后,响应速度变慢

View File

@@ -36,6 +36,8 @@ curl --location --request POST 'https://{{host}}/api/admin/initv485' \
3. 优化 - 原文件编码存取
4. 优化 - 文件夹读取,支持单个文件夹超出 100 个文件
5. 优化 - 问答拆分/手动录入,当有`a`字段时,自动将`q`作为补充索引。
6. 修复 - SSR渲染
7. 修复 - 定时任务无法实际关闭
8. 修复 - 输入引导特殊字符导致正则报错
6. 优化 - 对话框页面代码
7. 修复 - SSR渲染
8. 修复 - 定时任务无法实际关闭
9. 修复 - 输入引导特殊字符导致正则报错
10. 修复 - 文件包含特殊字符`%`,且为转义时会导致页面崩溃

View File

@@ -24,13 +24,15 @@ export function getSourceNameIcon({
sourceName: string;
sourceId?: string;
}) {
const fileIcon = getFileIcon(decodeURIComponent(sourceName), '');
if (fileIcon) {
return fileIcon;
}
if (strIsLink(sourceId)) {
return 'common/linkBlue';
}
try {
const fileIcon = getFileIcon(decodeURIComponent(sourceName.replace(/%/g, '%25')), '');
if (fileIcon) {
return fileIcon;
}
if (strIsLink(sourceId)) {
return 'common/linkBlue';
}
} catch (error) {}
return 'file/fill/manual';
}

View File

@@ -12,7 +12,7 @@ type Props = Omit<BoxProps, 'onChange'> & {
onChange: (e: string) => void;
};
const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
return (
<Box
display={'inline-flex'}
@@ -55,4 +55,4 @@ const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: P
);
};
export default RowTabs;
export default FillRowTabs;

View File

@@ -2,18 +2,24 @@ import React, { useMemo } from 'react';
import { Box, Flex, Grid, Image } from '@chakra-ui/react';
import type { FlexProps, GridProps } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyIcon from '../Icon';
// @ts-ignore
interface Props extends GridProps {
list: { id: string; icon?: string; label: string | React.ReactNode }[];
activeId: string;
type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
list: { icon?: string; label: string | React.ReactNode; value: ValueType }[];
value: ValueType;
size?: 'sm' | 'md' | 'lg';
inlineStyles?: FlexProps;
onChange: (id: string) => void;
}
onChange: (value: ValueType) => void;
};
const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }: Props) => {
const LightRowTabs = <ValueType = string,>({
list,
size = 'md',
value,
onChange,
inlineStyles,
...props
}: Props<ValueType>) => {
const { t } = useTranslation();
const sizeMap = useMemo(() => {
switch (size) {
@@ -49,7 +55,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
>
{list.map((item) => (
<Flex
key={item.id}
key={item.value as string}
py={sizeMap.inlineP}
alignItems={'center'}
justifyContent={'center'}
@@ -57,7 +63,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
px={3}
whiteSpace={'nowrap'}
{...inlineStyles}
{...(activeId === item.id
{...(value === item.value
? {
color: 'primary.600',
cursor: 'default',
@@ -68,8 +74,8 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
cursor: 'pointer'
})}
onClick={() => {
if (activeId === item.id) return;
onChange(item.id);
if (value === item.value) return;
onChange(item.value);
}}
>
{item.icon && (
@@ -88,4 +94,4 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
);
};
export default Tabs;
export default LightRowTabs;

View File

@@ -551,7 +551,8 @@ export const theme = extendTheme({
color: 'myGray.600',
fontWeight: 'normal',
height: '100%',
overflow: 'hidden'
overflow: 'hidden',
fontSize: '16px'
},
a: {
color: 'primary.600'

View File

@@ -364,6 +364,7 @@ const ChatInput = ({
color={'myGray.900'}
isDisabled={isSpeaking}
value={inputValue}
fontSize={['md', 'sm']}
onChange={(e) => {
const textarea = e.target;
textarea.style.height = textareaMinH;

View File

@@ -4,9 +4,8 @@ import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import Tabs from '../../Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import Markdown from '../../Markdown';
import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
@@ -142,7 +141,7 @@ export const ResponseBox = React.memo(function ResponseBox({
{t(item.moduleName)}
</Flex>
),
id: `${i}`
value: `${i}`
})),
[response, t]
);
@@ -155,7 +154,7 @@ export const ResponseBox = React.memo(function ResponseBox({
<>
{!hideTabs && (
<Box>
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
<LightRowTabs list={list} value={currentTab} onChange={setCurrentTab} />
</Box>
)}
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>

View File

@@ -997,7 +997,7 @@ const ChatBox = (
</Box>
</Box>
{/* message input */}
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && (
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && (
<ChatInput
onSendMessage={sendPrompt}
onStop={() => chatController.current?.abort('stop')}

View File

@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
import NextLink from 'next/link';
import Badge from '../Badge';

View File

@@ -1,7 +1,7 @@
import React, { useMemo } from 'react';
import { useRouter } from 'next/router';
import { Flex, Box } from '@chakra-ui/react';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useTranslation } from 'next-i18next';
import Badge from '../Badge';
import MyIcon from '@fastgpt/web/components/common/Icon';

View File

@@ -4,15 +4,20 @@ import type { GridProps } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d';
// @ts-ignore
export interface Props extends GridProps {
list: { id: string; label: string; icon: string }[];
activeId: string;
export type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
list: { value: ValueType; label: string; icon: string }[];
value: ValueType;
size?: 'sm' | 'md' | 'lg';
onChange: (id: string) => void;
}
onChange: (value: ValueType) => void;
};
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
const SideTabs = <ValueType = string,>({
list,
size = 'md',
value,
onChange,
...props
}: Props<ValueType>) => {
const sizeMap = useMemo(() => {
switch (size) {
case 'sm':
@@ -37,14 +42,14 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
<Box fontSize={sizeMap.fontSize} {...props}>
{list.map((item) => (
<Flex
key={item.id}
key={item.value as string}
py={sizeMap.inlineP}
borderRadius={'md'}
px={3}
mb={2}
fontWeight={'medium'}
alignItems={'center'}
{...(activeId === item.id
{...(value === item.value
? {
bg: ' primary.100 !important',
color: 'primary.600 ',
@@ -59,8 +64,8 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
bg: 'myGray.100'
}}
onClick={() => {
if (activeId === item.id) return;
onChange(item.id);
if (value === item.value) return;
onChange(item.value);
}}
>
<MyIcon mr={2} name={item.icon as IconNameType} w={'20px'} />
@@ -71,4 +76,4 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
);
};
export default React.memo(SideTabs);
export default SideTabs;

View File

@@ -136,7 +136,7 @@ const SelectOneResource = ({
</Flex>
)}
<Avatar ml={index !== 0 ? '0.5rem' : 0} src={item.avatar} w={'1.25rem'} />
<Box fontSize={'sm'} ml={2}>
<Box fontSize={['md', 'sm']} ml={2}>
{item.name}
</Box>
</Flex>

View File

@@ -23,7 +23,7 @@ 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 Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast';
@@ -127,27 +127,27 @@ const DatasetParamsModal = ({
w={['90vw', '550px']}
>
<ModalBody flex={'auto'} overflow={'auto'}>
<Tabs
<LightRowTabs<SearchSettingTabEnum>
mb={3}
list={[
{
icon: 'modal/setting',
label: t('core.dataset.search.search mode'),
id: SearchSettingTabEnum.searchMode
value: SearchSettingTabEnum.searchMode
},
{
icon: 'support/outlink/apikeyFill',
label: t('core.dataset.search.Filter'),
id: SearchSettingTabEnum.limit
value: SearchSettingTabEnum.limit
},
{
label: t('core.module.template.Query extension'),
id: SearchSettingTabEnum.queryExtension,
value: SearchSettingTabEnum.queryExtension,
icon: '/imgs/workflow/cfr.svg'
}
]}
activeId={currentTabType}
onChange={(e) => setCurrentTabType(e as any)}
value={currentTabType}
onChange={setCurrentTabType}
/>
{currentTabType === SearchSettingTabEnum.searchMode && (
<>

View File

@@ -3,7 +3,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useContextSelector } from 'use-context-selector';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
@@ -15,6 +14,7 @@ import { TeamModalContext } from './context';
import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { delLeaveTeam } from '@/web/support/user/team/api';
import dynamic from 'next/dynamic';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
enum TabListEnum {
member = 'member',
@@ -124,14 +124,12 @@ function TeamCard() {
</Flex>
<Flex px={5} alignItems={'center'} justifyContent={'space-between'}>
<RowTabs
<LightRowTabs<TabListEnum>
overflow={'auto'}
list={Tablist}
value={tab}
onChange={(v) => {
setTab(v as TabListEnum);
}}
></RowTabs>
onChange={setTab}
></LightRowTabs>
{/* ctrl buttons */}
<Flex alignItems={'center'}>
{tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && (

View File

@@ -51,6 +51,7 @@ export type GetHistoriesProps = OutLinkChatAuthProps & {
export type UpdateHistoryProps = OutLinkChatAuthProps & {
appId: string;
chatId: string;
title?: string;
customTitle?: string;
top?: boolean;
};

View File

@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { Box, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRouter } from 'next/router';
import dynamic from 'next/dynamic';
@@ -7,7 +7,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import PageContainer from '@/components/PageContainer';
import SideTabs from '@/components/SideTabs';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import UserInfo from './components/Info';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
@@ -31,7 +31,7 @@ enum TabEnum {
'loginout' = 'loginout'
}
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
const { t } = useTranslation();
const { userInfo, setUserInfo } = useUserStore();
const { feConfigs, isPc, systemVersion } = useSystemStore();
@@ -40,14 +40,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/user/userLight',
label: t('user.Personal Information'),
id: TabEnum.info
value: TabEnum.info
},
...(feConfigs?.isPlus
? [
{
icon: 'support/usage/usageRecordLight',
label: t('user.Usage Record'),
id: TabEnum.usage
value: TabEnum.usage
}
]
: []),
@@ -56,7 +56,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/bill/payRecordLight',
label: t('support.wallet.Bills'),
id: TabEnum.bill
value: TabEnum.bill
}
]
: []),
@@ -66,7 +66,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/account/promotionLight',
label: t('user.Promotion Record'),
id: TabEnum.promotion
value: TabEnum.promotion
}
]
: []),
@@ -75,21 +75,21 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/outlink/apikeyLight',
label: t('user.apikey.key'),
id: TabEnum.apikey
value: TabEnum.apikey
}
]
: []),
{
icon: 'support/user/individuation',
label: t('support.account.Individuation'),
id: TabEnum.individuation
value: TabEnum.individuation
},
...(feConfigs.isPlus
? [
{
icon: 'support/user/informLight',
label: t('user.Notice'),
id: TabEnum.inform
value: TabEnum.inform
}
]
: []),
@@ -97,7 +97,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
{
icon: 'support/account/loginoutLight',
label: t('user.Sign Out'),
id: TabEnum.loginout
value: TabEnum.loginout
}
];
@@ -139,13 +139,13 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
flex={'0 0 200px'}
borderRight={theme.borders.base}
>
<SideTabs
<SideTabs<TabEnum>
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
list={tabList}
activeId={currentTab}
value={currentTab}
onChange={setCurrentTab}
/>
<Flex alignItems={'center'}>
@@ -157,14 +157,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Flex>
) : (
<Box mb={3}>
<Tabs
<LightRowTabs<TabEnum>
m={'auto'}
size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({
id: item.id,
value: item.value,
label: item.label
}))}
activeId={currentTab}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>

View File

@@ -8,7 +8,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
/* update chat top, custom title */
async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiResponse) {
const { appId, chatId, customTitle, top } = req.body;
const { appId, chatId, title, customTitle, top } = req.body;
await autChatCrud({
req,
authToken: true,
@@ -20,6 +20,7 @@ async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiRes
{ appId, chatId },
{
updateTime: new Date(),
...(title !== undefined && { title }),
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })
}

View File

@@ -19,7 +19,7 @@ import {
Switch,
Textarea
} from '@chakra-ui/react';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
@@ -97,7 +97,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
>
{/* Header: row and search */}
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
<RowTabs
<FillRowTabs
list={[
{
icon: 'core/modules/teamPlugin',

View File

@@ -15,7 +15,7 @@ import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
@@ -143,7 +143,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
>
<Box pl={'20px'} mb={3} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs
<FillRowTabs
list={[
{
icon: 'core/modules/basicNode',

View File

@@ -21,11 +21,10 @@ import {
} from '@chakra-ui/react';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { useTranslation } from 'next-i18next';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import { useToast } from '@fastgpt/web/hooks/useToast';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
@@ -310,9 +309,9 @@ export function RenderHttpProps({
label={t('core.module.http.Props tip', { variable: variableText })}
></QuestionTip>
</Flex>
<Tabs
<LightRowTabs<TabEnum>
list={[
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
{ label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params },
...(!['GET', 'DELETE'].includes(requestMethods)
? [
{
@@ -322,14 +321,17 @@ export function RenderHttpProps({
{jsonBody?.value && <Box ml={1}></Box>}
</Flex>
),
id: TabEnum.body
value: TabEnum.body
}
]
: []),
{ label: <RenderPropsItem text="Headers" num={headersLength} />, id: TabEnum.headers }
{
label: <RenderPropsItem text="Headers" num={headersLength} />,
value: TabEnum.headers
}
]}
activeId={selectedTab}
onChange={(e) => setSelectedTab(e as any)}
value={selectedTab}
onChange={setSelectedTab}
/>
<Box bg={'white'} borderRadius={'md'}>
{params &&

View File

@@ -1,8 +1,7 @@
import React, { useCallback, useState } from 'react';
import { Box, Flex, Button, useDisclosure, HStack } from '@chakra-ui/react';
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { serviceSideProps } from '@/web/common/utils/i18n';
import PageContainer from '@/components/PageContainer';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useI18n } from '@/web/context/I18n';
import { useTranslation } from 'next-i18next';
@@ -32,8 +31,8 @@ import {
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { CreateAppType } from './components/CreateModal';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import Tabs from '@/components/Tabs';
import MyBox from '@fastgpt/web/components/common/MyBox';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
const CreateModal = dynamic(() => import('./components/CreateModal'));
const EditFolderModal = dynamic(
@@ -121,26 +120,26 @@ const MyApps = () => {
alignItems={'center'}
justifyContent={'space-between'}
>
<Tabs
<LightRowTabs
list={[
{
label: appT('type.All'),
id: 'ALL'
value: 'ALL'
},
{
label: appT('type.Simple bot'),
id: AppTypeEnum.simple
value: AppTypeEnum.simple
},
{
label: appT('type.Workflow bot'),
id: AppTypeEnum.workflow
value: AppTypeEnum.workflow
},
{
label: appT('type.Plugin'),
id: AppTypeEnum.plugin
value: AppTypeEnum.plugin
}
]}
activeId={appType}
value={appType}
inlineStyles={{ px: 0.5 }}
gap={5}
display={'flex'}

View File

@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
const ChatHeader = ({
history,
@@ -16,8 +18,7 @@ const ChatHeader = ({
appAvatar,
chatModels,
showHistory,
onRoute2AppDetail,
onOpenSlider
onRoute2AppDetail
}: {
history: ChatItemType[];
appName: string;
@@ -25,7 +26,6 @@ const ChatHeader = ({
chatModels?: string[];
showHistory?: boolean;
onRoute2AppDetail?: () => void;
onOpenSlider: () => void;
}) => {
const theme = useTheme();
const { t } = useTranslation();
@@ -36,6 +36,8 @@ const ChatHeader = ({
[appName, history, t]
);
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
return (
<Flex
alignItems={'center'}

View File

@@ -8,7 +8,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import Tabs from '@/components/Tabs';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useUserStore } from '@/web/support/user/useUserStore';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useI18n } from '@/web/context/I18n';
@@ -20,6 +20,9 @@ import {
} from '@fastgpt/global/common/parentFolder/type';
import { getMyApps } from '@/web/core/app/api';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useContextSelector } from 'use-context-selector';
import { ChatContext } from '@/web/core/chat/context/chatContext';
import MyBox from '@fastgpt/web/components/common/MyBox';
type HistoryItemType = {
id: string;
@@ -38,30 +41,22 @@ const ChatHistorySlider = ({
appId,
appName,
appAvatar,
history,
apps = [],
confirmClearText,
activeChatId,
onChangeChat,
onDelHistory,
onClearHistory,
onSetHistoryTop,
onSetCustomTitle,
onClose
onSetCustomTitle
}: {
appId?: string;
appName: string;
appAvatar: string;
history: HistoryItemType[];
activeChatId: string;
apps?: AppListItemType[];
confirmClearText: string;
onChangeChat: (chatId?: string) => void;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
onClose: () => void;
}) => {
const theme = useTheme();
const router = useRouter();
@@ -73,7 +68,28 @@ const ChatHistorySlider = ({
const { isPc } = useSystemStore();
const { userInfo } = useUserStore();
const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.history);
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.history);
const {
histories,
onChangeChatId,
onChangeAppId,
chatId: activeChatId,
isLoading
} = useContextSelector(ChatContext, (v) => v);
const concatHistory = useMemo(() => {
const formatHistories: HistoryItemType[] = histories.map((item) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}));
const newChat: HistoryItemType = { id: activeChatId, title: t('core.chat.New Chat') };
const activeChat = histories.find((item) => item.chatId === activeChatId);
return !activeChat ? [newChat].concat(formatHistories) : formatHistories;
}, [activeChatId, histories, t]);
const showApps = apps?.length > 0;
@@ -86,15 +102,6 @@ const ChatHistorySlider = ({
content: confirmClearText
});
const concatHistory = useMemo<HistoryItemType[]>(
() =>
!activeChatId
? //@ts-ignore
[{ id: activeChatId, title: t('core.chat.New Chat') }].concat(history)
: history,
[activeChatId, history, t]
);
const canRouteToDetail = useMemo(
() => appId && userInfo?.team.permission.hasWritePer,
[appId, userInfo?.team.permission.hasWritePer]
@@ -111,22 +118,10 @@ const ChatHistorySlider = ({
);
}, []);
const onChangeApp = useCallback(
(appId: string) => {
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});
},
[router]
);
return (
<Flex
position={'relative'}
<MyBox
isLoading={isLoading}
display={'flex'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
@@ -162,27 +157,27 @@ const ChatHistorySlider = ({
{/* menu */}
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5} alignItems={'center'}>
{!isPc && appId && (
<Tabs
<LightRowTabs<TabEnum>
flex={'1 0 0'}
mr={2}
list={[
{ label: t('core.chat.Recent use'), id: TabEnum.recently },
{ label: t('App'), id: TabEnum.app },
{ label: t('core.chat.History'), id: TabEnum.history }
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
{ label: t('App'), value: TabEnum.app },
{ label: t('core.chat.History'), value: TabEnum.history }
]}
activeId={currentTab}
onChange={(e) => setCurrentTab(e as `${TabEnum}`)}
value={currentTab}
onChange={setCurrentTab}
/>
)}
<Button
variant={'whitePrimary'}
flex={['0', 1]}
flex={['0 0 auto', 1]}
h={'100%'}
color={'primary.600'}
borderRadius={'xl'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
overflow={'hidden'}
onClick={() => onChangeChat()}
onClick={() => onChangeChatId()}
>
{t('core.chat.New Chat')}
</Button>
@@ -195,7 +190,11 @@ const ChatHistorySlider = ({
size={'mdSquare'}
aria-label={''}
borderRadius={'50%'}
onClick={openConfirm(onClearHistory)}
onClick={() =>
openConfirm(() => {
onClearHistory();
})()
}
>
<MyIcon name={'common/clearLight'} w={'16px'} />
</IconButton>
@@ -232,7 +231,7 @@ const ChatHistorySlider = ({
}
: {
onClick: () => {
onChangeChat(item.id);
onChangeChatId(item.id);
}
})}
>
@@ -292,7 +291,7 @@ const ChatHistorySlider = ({
onClick: () => {
onDelHistory({ chatId: item.id });
if (item.id === activeChatId) {
onChangeChat();
onChangeChatId();
}
},
type: 'danger'
@@ -324,10 +323,7 @@ const ChatHistorySlider = ({
color: 'primary.600'
}
: {
onClick: () => {
onChangeApp(item._id);
onClose();
}
onClick: () => onChangeAppId(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
@@ -344,8 +340,7 @@ const ChatHistorySlider = ({
value={appId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
onChangeAppId(id);
}}
server={getAppList}
/>
@@ -377,7 +372,7 @@ const ChatHistorySlider = ({
)}
<EditTitleModal />
<ConfirmModal />
</Flex>
</MyBox>
);
};

View File

@@ -7,13 +7,15 @@ import Avatar from '@/components/Avatar';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import MyDivider from '@fastgpt/web/components/common/MyDivider';
import MyPopover from '@fastgpt/web/components/common/MyPopover/index';
import SelectOneResource from '@/components/common/folder/SelectOneResource';
import { getMyApps } from '@/web/core/app/api';
import {
GetResourceFolderListProps,
GetResourceListItemResponse
} from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import dynamic from 'next/dynamic';
const SelectOneResource = dynamic(() => import('@/components/common/folder/SelectOneResource'));
const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppId: string }) => {
const { t } = useTranslation();
@@ -74,19 +76,19 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
{!isTeamChat && (
<>
<MyDivider h={2} my={1} />
<MyPopover
placement="right-start"
offset={[30, -65]}
trigger="hover"
Trigger={
<HStack
px={4}
my={2}
color={'myGray.500'}
fontSize={'sm'}
justifyContent={'space-between'}
>
<Box>{t('core.chat.Recent use')}</Box>
<HStack
px={4}
my={2}
color={'myGray.500'}
fontSize={'sm'}
justifyContent={'space-between'}
>
<Box>{t('core.chat.Recent use')}</Box>
<MyPopover
placement="bottom-end"
offset={[20, 10]}
trigger="hover"
Trigger={
<HStack
spacing={0.5}
cursor={'pointer'}
@@ -102,23 +104,23 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
<Box>{t('common.More')}</Box>
<MyIcon name={'common/select'} w={'1rem'} />
</HStack>
</HStack>
}
>
{({ onClose }) => (
<Box minH={'200px'}>
<SelectOneResource
value={activeAppId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
}}
server={getAppList}
/>
</Box>
)}
</MyPopover>
}
>
{({ onClose }) => (
<Box minH={'200px'}>
<SelectOneResource
value={activeAppId}
onSelect={(id) => {
if (!id) return;
onChangeApp(id);
onClose();
}}
server={getAppList}
/>
</Box>
)}
</MyPopover>
</HStack>
</>
)}

View File

@@ -1,25 +1,12 @@
import React, { useCallback, useRef } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { useTranslation } from 'next-i18next';
import ChatBox from '@/components/ChatBox';
@@ -29,7 +16,6 @@ import SideBar from '@/components/SideBar';
import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps';
import ChatHeader from './components/ChatHeader';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
@@ -39,40 +25,48 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
import { useMount } from 'ahooks';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
type Props = { appId: string; chatId: string };
const Chat = ({
appId,
chatId,
myApps
}: Props & {
myApps: AppListItemType[];
}) => {
const router = useRouter();
const theme = useTheme();
const { t } = useTranslation();
const { toast } = useToast();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { setLastChatAppId } = useChatStore();
const {
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
clearHistories,
chatData,
setChatData,
delOneHistoryItem
} = useChatStore();
const { userInfo } = useUserStore();
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const { userInfo } = useUserStore();
const { isPc } = useSystemStore();
const { Loading, setIsLoading } = useLoading();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
const completionChatId = chatId ? chatId : nanoid();
const completionChatId = chatId ? chatId : getNanoid();
const { responseText, responseData } = await streamFetch({
data: {
@@ -89,170 +83,82 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
chatId: completionChatId,
appId
}
});
onChangeChatId(completionChatId, true);
loadHistories();
}
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle
});
onUpdateHistory({
appId,
chatId: completionChatId,
title: newTitle
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
title: newTitle
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
);
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
);
// get chat app info
const loadChatInfo = useCallback(
async ({
appId,
chatId,
loading = false
}: {
appId: string;
chatId: string;
loading?: boolean;
}) => {
try {
loading && setIsLoading(true);
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { loading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
setChatData({
...res,
history
});
const res = await getInitChatInfo({ appId, chatId });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}));
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
// reset all chat tore
const result: InitChatResponse = {
...res,
history
};
// reset chat box
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setLastChatAppId(appId);
setChatData(result);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId('');
setLastChatId('');
toast({
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
// reset all chat tore
if (e?.code === 501) {
router.replace('/app/list');
} else if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
setIsLoading(false);
return null;
},
[setIsLoading, setChatData, setLastChatAppId, setLastChatId, toast, t, router]
}
);
// 初始化聊天框
useQuery(['init', { appId, chatId }], () => {
// pc: redirect to latest model chat
if (!appId && lastChatAppId) {
return router.replace({
query: {
appId: lastChatAppId,
chatId: lastChatId
}
});
}
if (!appId && myApps[0]) {
return router.replace({
query: {
appId: myApps[0]._id,
chatId: lastChatId
}
});
}
if (!appId) {
(async () => {
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
appId: apps[0]._id,
chatId: lastChatId
}
});
}
})();
return;
}
// store id
appId && setLastChatAppId(appId);
setLastChatId(chatId);
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadChatInfo({
appId,
chatId,
loading: appId !== chatData.appId
});
});
useQuery(['loadHistories', appId], () => (appId ? loadHistories({ appId }) : null));
return (
<Flex h={'100%'}>
<NextHead title={chatData.app.name}></NextHead>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{/* pc always show history. */}
{((children: React.ReactNode) => {
@@ -285,42 +191,17 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId={appId}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
chatId: chatId || '',
appId
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId })}
onDelHistory={(e) => onDelHistory({ ...e, appId })}
onClearHistory={() => {
clearHistories({ appId });
router.replace({
query: {
appId
}
});
onClearHistories({ appId });
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, appId });
onUpdateHistory({ ...e, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
onUpdateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title
});
}}
@@ -340,7 +221,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appName={chatData.app.name}
history={chatData.history}
chatModels={chatData.app.chatModels}
onOpenSlider={onOpenSlider}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
showHistory
/>
@@ -356,19 +236,81 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'}
onStartChat={startChat}
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })}
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId}
chatId={chatId}
/>
</Box>
</Flex>
</Flex>
<Loading fixed={false} />
</PageContainer>
</Flex>
);
};
const Render = (props: Props) => {
const { appId } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { lastChatAppId, lastChatId } = useChatStore();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
);
const { data: histories = [], runAsync: loadHistories } = useRequest2(
() => (appId ? getChatHistories({ appId }) : Promise.resolve([])),
{
manual: false,
refreshDeps: [appId]
}
);
// 初始化聊天框
useMount(async () => {
// pc: redirect to latest model chat
if (!appId) {
if (lastChatAppId) {
return router.replace({
query: {
...router.query,
appId: lastChatAppId,
chatId: lastChatId
}
});
}
const apps = await loadMyApps();
if (apps.length === 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
router.replace('/app/list');
} else {
router.replace({
query: {
...router.query,
appId: apps[0]._id,
chatId: ''
}
});
}
}
});
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
);
};
export async function getServerSideProps(context: any) {
return {
props: {
@@ -379,4 +321,4 @@ export async function getServerSideProps(context: any) {
};
}
export default Chat;
export default Render;

View File

@@ -1,15 +1,13 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch';
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import SideBar from '@/components/SideBar';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getErrText } from '@fastgpt/global/common/error/utils';
import type { ChatHistoryItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@@ -21,27 +19,30 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next';
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { delChatRecordById, getChatHistories, getInitOutLinkChatInfo } from '@/web/core/chat/api';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import MyBox from '@fastgpt/web/components/common/MyBox';
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log';
import { connectToDatabase } from '@/service/mongo';
import NextHead from '@/components/common/NextHead';
import Head from 'next/head';
import { useContextSelector } from 'use-context-selector';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
import { useMount } from 'ahooks';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
const OutLink = ({
appName,
appIntro,
appAvatar
}: {
type Props = {
appName: string;
appIntro: string;
appAvatar: string;
}) => {
shareId: string;
authToken: string;
};
const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
const { t } = useTranslation();
const router = useRouter();
const {
@@ -58,28 +59,28 @@ const OutLink = ({
[key: string]: string;
};
const { toast } = useToast();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true);
const { localUId } = useShareChatStore();
const {
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const appId = chatData.appId;
const { localUId } = useShareChatStore();
const outLinkUid: string = authToken || localUId;
const {
loadHistories,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2);
@@ -115,101 +116,86 @@ const OutLink = ({
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
onChangeChatId(completionChatId, true);
loadHistories();
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle,
shareId,
outLinkUid
});
onUpdateHistory({
appId,
chatId: completionChatId,
title: newTitle,
shareId,
outLinkUid
});
}
// update chat window
setChatData((state) => ({
...state,
title: newTitle,
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
/* post message to report result */
const result: ChatSiteItemType[] = GPTMessages2Chats(prompts).map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: 'finish'
title: newTitle
}));
// hook message
window.top?.postMessage(
{
type: 'shareChatFinish',
data: {
question: result[0]?.value,
question: prompts[0]?.content,
answer: responseText
}
},
'*'
);
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
customVariables,
shareId,
outLinkUid,
setChatData,
appId,
pushHistory,
router,
histories,
updateHistory
forbidLoadChat,
onChangeChatId,
loadHistories,
onUpdateHistory,
appId
]
);
const loadChatInfo = useCallback(
async (shareId: string, chatId: string) => {
if (!shareId) return null;
const { loading } = useRequest2(
async () => {
if (!shareId || !outLinkUid || forbidLoadChat.current) return;
try {
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const res = await getInitOutLinkChatInfo({
chatId,
shareId,
outLinkUid
});
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
const result: InitChatResponse = {
...res,
history
};
setChatData({
...res,
history
});
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
// reset chat box
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setChatData(result);
},
{
manual: false,
refreshDeps: [shareId, outLinkUid, chatId],
onSuccess() {
// send init message
if (!initSign.current) {
initSign.current = true;
@@ -217,149 +203,87 @@ const OutLink = ({
window.top?.postMessage({ type: 'shareChatReady' }, '*');
}
}
if (chatId && res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
},
onError(e: any) {
console.log(e);
toast({
status: 'error',
title: getErrText(e, t('core.shareChat.Init Error'))
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
});
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
return null;
},
[outLinkUid, router, setChatData, t, toast]
}
);
const { isFetching } = useQuery(['init', shareId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
return loadChatInfo(shareId, chatId);
});
// load histories
useQuery(['loadHistories', outLinkUid, shareId], () => {
if (shareId && outLinkUid) {
return loadHistories({
shareId,
outLinkUid
});
}
return null;
});
// window init
useEffect(() => {
useMount(() => {
setIdEmbed(window !== top);
}, []);
});
return (
<>
<NextHead title={appName} desc={appIntro} icon={appAvatar} />
<PageContainer
isLoading={loading}
{...(isEmbed
? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } }
: { p: [0, 5] })}
>
<MyBox
isLoading={isFetching}
h={'100%'}
display={'flex'}
flexDirection={['column', 'row']}
bg={'white'}
>
{showHistory === '1'
? ((children: React.ReactNode) => {
return isPc ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{children}
</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('core.chat.Confirm to clear share chat history')}
activeChatId={chatId}
history={histories.map((item) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{showHistory === '1' &&
((children: React.ReactNode) => {
return isPc ? (
<SideBar>{children}</SideBar>
) : (
<Drawer
isOpen={isOpenSlider}
placement="left"
autoFocus={false}
size={'xs'}
onClose={onCloseSlider}
onChangeChat={(chatId) => {
router.replace({
query: {
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={({ chatId }) =>
delOneHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
clearHistories({ shareId, outLinkUid });
router.replace({
query: {
...router.query,
chatId: ''
}
});
}}
onSetHistoryTop={(e) => {
updateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={async (e) => {
updateHistory({
appId: chatData.appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
)
: null}
>
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
{children}
</DrawerContent>
</Drawer>
);
})(
<ChatHistorySlider
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('core.chat.Confirm to clear share chat history')}
onDelHistory={({ chatId }) =>
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
}
onClearHistory={() => {
onClearHistories({ shareId, outLinkUid });
}}
onSetHistoryTop={(e) => {
onUpdateHistory({
...e,
appId: chatData.appId,
shareId,
outLinkUid
});
}}
onSetCustomTitle={(e) => {
onUpdateHistory({
appId: chatData.appId,
chatId: e.chatId,
customTitle: e.title,
shareId,
outLinkUid
});
}}
/>
)}
{/* chat container */}
<Flex
@@ -375,12 +299,10 @@ const OutLink = ({
appName={chatData.app.name}
history={chatData.history}
showHistory={showHistory === '1'}
onOpenSlider={onOpenSlider}
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
@@ -389,8 +311,14 @@ const OutLink = ({
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, shareId, outLinkUid })
onDelMessage={({ contentId }) =>
delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
shareId,
outLinkUid
})
}
appId={chatData.appId}
chatId={chatId}
@@ -399,16 +327,37 @@ const OutLink = ({
/>
</Box>
</Flex>
</MyBox>
</Flex>
</PageContainer>
</>
);
};
export default OutLink;
const Render = (props: Props) => {
const { shareId, authToken } = props;
const { localUId } = useShareChatStore();
const outLinkUid: string = authToken || localUId;
const { data: histories = [], runAsync: loadHistories } = useRequest2(
() => (shareId && outLinkUid ? getChatHistories({ shareId, outLinkUid }) : Promise.resolve([])),
{
manual: false,
refreshDeps: [shareId, outLinkUid]
}
);
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<OutLink {...props} />;
</ChatContextProvider>
);
};
export default Render;
export async function getServerSideProps(context: any) {
const shareId = context?.query?.shareId || '';
const authToken = context?.query?.authToken || '';
const app = await (async () => {
try {
@@ -433,6 +382,8 @@ export async function getServerSideProps(context: any) {
appName: app?.appId?.name ?? 'name',
appAvatar: app?.appId?.avatar ?? '',
appIntro: app?.appId?.intro ?? 'intro',
shareId: shareId ?? '',
authToken: authToken ?? '',
...(await serviceSideProps(context, ['file']))
}
};

View File

@@ -1,18 +1,9 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import NextHead from '@/components/common/NextHead';
import { getTeamChatInfo } from '@/web/core/chat/api';
import { delChatRecordById, getChatHistories, getTeamChatInfo } from '@/web/core/chat/api';
import { useRouter } from 'next/router';
import {
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer';
@@ -22,21 +13,26 @@ import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { streamFetch } from '@/web/common/api/fetch';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
import { AppListItemType } from '@fastgpt/global/core/app/type';
import { useContextSelector } from 'use-context-selector';
import { InitChatResponse } from '@/global/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
const OutLink = () => {
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
const { t } = useTranslation();
const router = useRouter();
const {
@@ -45,11 +41,7 @@ const OutLink = () => {
chatId = '',
teamToken,
...customVariables
} = router.query as {
teamId: string;
appId: string;
chatId: string;
teamToken: string;
} = router.query as Props & {
[key: string]: string;
};
@@ -57,22 +49,20 @@ const OutLink = () => {
const theme = useTheme();
const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const {
chatData,
setChatData,
histories,
loadHistories,
lastChatAppId,
lastChatId,
pushHistory,
updateHistory,
delOneHistory,
delOneHistoryItem,
clearHistories
} = useChatStore();
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId,
onChangeAppId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@@ -99,34 +89,16 @@ const OutLink = () => {
// new chat
if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true;
router.replace({
query: {
...router.query,
chatId: completionChatId
}
});
}
onChangeChatId(completionChatId, true);
loadHistories();
} else {
// update chat
const currentChat = histories.find((item) => item.chatId === chatId);
currentChat &&
updateHistory({
...currentChat,
updateTime: new Date(),
title: newTitle,
teamId,
teamToken
});
onUpdateHistory({
appId: chatData.appId,
chatId: completionChatId,
title: newTitle,
teamId,
teamToken
});
}
// update chat window
setChatData((state) => ({
@@ -135,7 +107,7 @@ const OutLink = () => {
history: ChatBoxRef.current?.getChatHistories() || state.history
}));
return { responseText, responseData, isNewChat: forbidRefresh.current };
return { responseText, responseData, isNewChat: forbidLoadChat.current };
},
[
chatId,
@@ -143,132 +115,63 @@ const OutLink = () => {
appId,
teamId,
teamToken,
setChatData,
pushHistory,
router,
histories,
updateHistory
forbidLoadChat,
onChangeChatId,
loadHistories,
onUpdateHistory,
chatData.appId
]
);
/* replace router query to last chat */
useEffect(() => {
if ((!chatId || !appId) && (lastChatId || lastChatAppId)) {
router.replace({
query: {
...router.query,
chatId: chatId || lastChatId,
appId: appId || lastChatAppId
}
});
}
}, []);
// get chat app list
const loadApps = useCallback(async () => {
try {
const apps = await getMyTokensApps({ teamId, teamToken });
if (apps.length <= 0) {
toast({
status: 'error',
title: t('core.chat.You need to a chat app')
});
return [];
}
// if app id not exist, redirect to first app
if (!appId || !apps.find((item) => item._id === appId)) {
router.replace({
query: {
...router.query,
appId: apps[0]?._id
}
});
}
return apps;
} catch (error: any) {
toast({
status: 'warning',
title: getErrText(error)
});
}
return [];
}, [appId, teamToken, router, teamId, t, toast]);
const { data: myApps = [], isLoading: isLoadingApps } = useQuery(['initApps', teamId], () => {
if (!teamId) {
toast({
status: 'error',
title: t('support.user.team.tag.Have not opened')
});
return;
}
return loadApps();
});
// load histories
useQuery(['loadHistories', appId], () => {
if (teamId && appId) {
return loadHistories({ teamId, appId, teamToken: teamToken });
}
return;
});
// get chat app info
const loadChatInfo = useCallback(async () => {
try {
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken });
const { loading } = useRequest2(
async () => {
if (!appId || forbidLoadChat.current) return;
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
const history = res.history.map((item) => ({
...item,
dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish
}));
setChatData({
const result: InitChatResponse = {
...res,
history
});
};
// have records.
ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
toast({
title: t('core.chat.Failed to initialize chat'),
status: 'error'
});
if (chatId) {
router.replace({
query: {
...router.query,
chatId: ''
}
setChatData(result);
},
{
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
toast({
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
if (chatId) {
onChangeChatId('');
}
},
onFinally() {
forbidLoadChat.current = false;
}
}
return null;
}, [teamId, appId, chatId, teamToken, setChatData, toast, t, router]);
const { isFetching } = useQuery(['init', teamId, appId, chatId], () => {
if (forbidRefresh.current) {
forbidRefresh.current = false;
return null;
}
if (teamId && appId) {
return loadChatInfo();
}
return null;
});
);
return (
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}>
<NextHead title={chatData.app.name}></NextHead>
<Flex h={'100%'}>
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */}
{isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@@ -276,7 +179,7 @@ const OutLink = () => {
</Box>
)}
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => {
return isPc || !appId ? (
@@ -299,44 +202,18 @@ const OutLink = () => {
apps={myApps}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
activeChatId={chatId}
confirmClearText={t('core.chat.Confirm to clear history')}
onClose={onCloseSlider}
history={histories.map((item, i) => ({
id: item.chatId,
title: item.title,
customTitle: item.customTitle,
top: item.top
}))}
onChangeChat={(chatId) => {
router.replace({
query: {
...router.query,
chatId: chatId || ''
}
});
if (!isPc) {
onCloseSlider();
}
}}
onDelHistory={(e) => delOneHistory({ ...e, appId, teamId, teamToken })}
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
onClearHistory={() => {
clearHistories({ appId, teamId, teamToken });
router.replace({
query: {
...router.query,
chatId: ''
}
});
onClearHistories({ appId, teamId, teamToken });
}}
onSetHistoryTop={(e) => {
updateHistory({ ...e, teamId, teamToken, appId });
onUpdateHistory({ ...e, teamId, teamToken, appId });
}}
onSetCustomTitle={async (e) => {
updateHistory({
onUpdateHistory({
appId,
chatId: e.chatId,
title: e.title,
customTitle: e.title,
teamId,
teamToken
@@ -357,13 +234,11 @@ const OutLink = () => {
appAvatar={chatData.app.avatar}
appName={chatData.app.name}
history={chatData.history}
onOpenSlider={onOpenSlider}
showHistory
/>
{/* chat box */}
<Box flex={1}>
<ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef}
appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar}
@@ -372,8 +247,8 @@ const OutLink = () => {
feedbackType={'user'}
onUpdateVariable={(e) => {}}
onStartChat={startChat}
onDelMessage={(e) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken })
onDelMessage={({ contentId }) =>
delChatRecordById({ contentId, appId: chatData.appId, chatId, teamId, teamToken })
}
appId={chatData.appId}
chatId={chatId}
@@ -384,16 +259,73 @@ const OutLink = () => {
</Flex>
</Flex>
</PageContainer>
</MyBox>
</Flex>
);
};
const Render = (props: Props) => {
const { teamId, appId, teamToken } = props;
const { t } = useTranslation();
const { toast } = useToast();
const router = useRouter();
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
async () => {
if (teamId && teamToken) {
return getMyTokensApps({ teamId, teamToken });
}
return [];
},
{
manual: false
}
);
const { data: histories = [], runAsync: loadHistories } = useRequest2(
async () => {
if (teamId && appId && teamToken) {
return getChatHistories({ teamId, appId, teamToken: teamToken });
}
return [];
},
{
manual: false,
refreshDeps: [appId, teamId, teamToken]
}
);
// 初始化聊天框
useEffect(() => {
(async () => {
if (appId || myApps.length === 0) return;
router.replace({
query: {
...router.query,
appId: myApps[0]._id,
chatId: ''
}
});
})();
}, [appId, loadMyApps, myApps, router, t, toast]);
return (
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
<Chat {...props} myApps={myApps} />
</ChatContextProvider>
);
};
export async function getServerSideProps(context: any) {
return {
props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
teamId: context?.query?.teamId || '',
teamToken: context?.query?.teamToken || '',
...(await serviceSideProps(context, ['file']))
}
};
}
export default OutLink;
export default Render;

View File

@@ -75,16 +75,16 @@ const InputDataModal = ({
});
const tabList = [
{ label: t('dataset.data.edit.Content'), id: TabEnum.content, icon: 'common/overviewLight' },
{ label: t('dataset.data.edit.Content'), value: TabEnum.content, icon: 'common/overviewLight' },
{
label: t('dataset.data.edit.Index', { amount: indexes.length }),
id: TabEnum.index,
value: TabEnum.index,
icon: 'kbTest'
},
...(dataId
? [{ label: t('dataset.data.edit.Delete'), id: TabEnum.delete, icon: 'delete' }]
? [{ label: t('dataset.data.edit.Delete'), value: TabEnum.delete, icon: 'delete' }]
: []),
{ label: t('dataset.data.edit.Course'), id: TabEnum.doc, icon: 'common/courseLight' }
{ label: t('dataset.data.edit.Course'), value: TabEnum.doc, icon: 'common/courseLight' }
];
const { ConfirmModal, openConfirm } = useConfirm({
@@ -243,10 +243,10 @@ const InputDataModal = ({
mb={6}
fontSize={'sm'}
/>
<SideTabs
<SideTabs<TabEnum>
list={tabList}
activeId={currentTab}
onChange={async (e: any) => {
value={currentTab}
onChange={async (e) => {
if (e === TabEnum.delete) {
return openConfirm(onDeleteData)();
}

View File

@@ -8,9 +8,9 @@ import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
import MyIcon from '@fastgpt/web/components/common/Icon';
import SideTabs from '@/components/SideTabs';
import { useRouter } from 'next/router';
import Tabs from '@/components/Tabs';
import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useI18n } from '@/web/context/I18n';
export enum TabEnum {
@@ -34,12 +34,12 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
const tabList = [
{
label: t('core.dataset.Collection'),
id: TabEnum.collectionCard,
value: TabEnum.collectionCard,
icon: 'common/overviewLight'
},
{ label: t('core.dataset.test.Search Test'), id: TabEnum.test, icon: 'kbTest' },
{ label: t('core.dataset.test.Search Test'), value: TabEnum.test, icon: 'kbTest' },
...(datasetDetail.permission.hasManagePer
? [{ label: t('common.Config'), id: TabEnum.info, icon: 'common/settingLight' }]
? [{ label: t('common.Config'), value: TabEnum.info, icon: 'common/settingLight' }]
: [])
];
@@ -78,16 +78,14 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
</Flex>
)}
</Box>
<SideTabs
<SideTabs<TabEnum>
px={4}
flex={1}
mx={'auto'}
w={'100%'}
list={tabList}
activeId={currentTab}
onChange={(e: any) => {
setCurrentTab(e);
}}
value={currentTab}
onChange={setCurrentTab}
/>
<Box px={4}>
{rebuildingCount > 0 && (
@@ -149,16 +147,13 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
</Flex>
) : (
<Box mb={3}>
<Tabs
<LightRowTabs<TabEnum>
m={'auto'}
w={'260px'}
size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({
id: item.id,
label: item.label
}))}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
list={tabList}
value={currentTab}
onChange={setCurrentTab}
/>
</Box>
)}

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api';

View File

@@ -5,7 +5,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n';

View File

@@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
import { useChatStore } from '@/web/core/chat/storeChat';
import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api';

View File

@@ -43,7 +43,7 @@ export const delChatHistoryById = (data: DelHistoryProps) => DELETE(`/core/chat/
/**
* clear all history by appid
*/
export const clearChatHistoryByAppId = (data: ClearHistoriesProps) =>
export const delClearChatHistories = (data: ClearHistoriesProps) =>
DELETE(`/core/chat/clearHistories`, data);
/**

View File

@@ -0,0 +1,151 @@
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useRouter } from 'next/router';
import React, { ReactNode, useCallback, useEffect, useRef } from 'react';
import { createContext } from 'use-context-selector';
import { delClearChatHistories, delChatHistoryById, putChatHistory } from '../api';
import { ChatHistoryItemType } from '@fastgpt/global/core/chat/type';
import { ClearHistoriesProps, DelHistoryProps, UpdateHistoryProps } from '@/global/core/chat/api';
import { useDisclosure } from '@chakra-ui/react';
import { useChatStore } from './storeChat';
type ChatContextValueType = {
histories: ChatHistoryItemType[];
loadHistories: () => Promise<ChatHistoryItemType[]>;
};
type ChatContextType = ChatContextValueType & {
chatId: string;
onUpdateHistory: (data: UpdateHistoryProps) => void;
onDelHistory: (data: DelHistoryProps) => Promise<undefined>;
onClearHistories: (data: ClearHistoriesProps) => Promise<undefined>;
isOpenSlider: boolean;
onCloseSlider: () => void;
onOpenSlider: () => void;
forbidLoadChat: React.MutableRefObject<boolean>;
onChangeChatId: (chatId?: string, forbid?: boolean) => void;
onChangeAppId: (appId: string) => void;
isLoading: boolean;
};
export const ChatContext = createContext<ChatContextType>({
chatId: '',
// forbidLoadChat: undefined,
histories: [],
loadHistories: function (): Promise<ChatHistoryItemType[]> {
throw new Error('Function not implemented.');
},
onUpdateHistory: function (data: UpdateHistoryProps): void {
throw new Error('Function not implemented.');
},
onDelHistory: function (data: DelHistoryProps): Promise<undefined> {
throw new Error('Function not implemented.');
},
onClearHistories: function (data: ClearHistoriesProps): Promise<undefined> {
throw new Error('Function not implemented.');
},
isOpenSlider: false,
onCloseSlider: function (): void {
throw new Error('Function not implemented.');
},
onOpenSlider: function (): void {
throw new Error('Function not implemented.');
},
forbidLoadChat: { current: false },
onChangeChatId: function (chatId?: string | undefined, forbid?: boolean | undefined): void {
throw new Error('Function not implemented.');
},
onChangeAppId: function (appId: string): void {
throw new Error('Function not implemented.');
},
isLoading: false
});
const ChatContextProvider = ({
children,
histories,
loadHistories
}: ChatContextValueType & { children: ReactNode }) => {
const router = useRouter();
const { chatId = '' } = router.query as { chatId: string };
const isSystemChat = router.pathname === '/chat';
const forbidLoadChat = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const { setLastChatId } = useChatStore();
const onChangeChatId = useCallback(
(changeChatId = '', forbid = false) => {
if (chatId !== changeChatId) {
forbidLoadChat.current = forbid;
setLastChatId(changeChatId);
router.replace({
query: {
...router.query,
chatId: changeChatId || ''
}
});
}
onCloseSlider();
},
[chatId, onCloseSlider, router, setLastChatId]
);
useEffect(() => {
setLastChatId(chatId);
}, [chatId, setLastChatId]);
const onChangeAppId = useCallback(
(appId: string) => {
router.replace({
query: {
...router.query,
chatId: '',
appId
}
});
onCloseSlider();
},
[onCloseSlider, router]
);
const { runAsync: onUpdateHistory, loading: isUpdatingHistory } = useRequest2(putChatHistory, {
onSuccess() {
loadHistories();
}
});
const { runAsync: onDelHistory, loading: isDeletingHistory } = useRequest2(delChatHistoryById, {
onSuccess() {
loadHistories();
}
});
const { runAsync: onClearHistories, loading: isClearingHistory } = useRequest2(
delClearChatHistories,
{
onSuccess() {
loadHistories();
},
onFinally() {
onChangeChatId('');
}
}
);
const isLoading = isUpdatingHistory || isDeletingHistory || isClearingHistory;
const contextValue = {
chatId,
histories,
loadHistories,
onUpdateHistory,
onDelHistory,
onClearHistories,
isOpenSlider,
onCloseSlider,
onOpenSlider,
forbidLoadChat,
onChangeChatId,
onChangeAppId,
isLoading
};
return <ChatContext.Provider value={contextValue}>{children}</ChatContext.Provider>;
};
export default ChatContextProvider;

View File

@@ -0,0 +1,39 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { DeleteChatItemProps } from '@/global/core/chat/api';
type State = {
lastChatAppId: string;
setLastChatAppId: (id: string) => void;
lastChatId: string;
setLastChatId: (id: string) => void;
};
export const useChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
lastChatAppId: '',
setLastChatAppId(id: string) {
set((state) => {
state.lastChatAppId = id;
});
},
lastChatId: '',
setLastChatId(id: string) {
set((state) => {
state.lastChatId = id;
});
}
})),
{
name: 'chatStore',
partialize: (state) => ({
lastChatAppId: state.lastChatAppId,
lastChatId: state.lastChatId
})
}
)
)
);

View File

@@ -1,147 +0,0 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
import type {
InitChatResponse,
GetHistoriesProps,
ClearHistoriesProps,
DelHistoryProps,
UpdateHistoryProps,
DeleteChatItemProps
} from '@/global/core/chat/api';
import {
delChatHistoryById,
getChatHistories,
clearChatHistoryByAppId,
delChatRecordById,
putChatHistory
} from '@/web/core/chat/api';
import { defaultChatData } from '@/global/core/chat/constants';
type State = {
histories: ChatHistoryItemType[];
loadHistories: (data: GetHistoriesProps) => Promise<null>;
delOneHistory(data: DelHistoryProps): Promise<void>;
clearHistories(data: ClearHistoriesProps): Promise<void>;
pushHistory: (history: ChatHistoryItemType) => void;
updateHistory: (e: UpdateHistoryProps & { updateTime?: Date; title?: string }) => Promise<any>;
chatData: InitChatResponse;
setChatData: (e: InitChatResponse | ((e: InitChatResponse) => InitChatResponse)) => void;
lastChatAppId: string;
setLastChatAppId: (id: string) => void;
lastChatId: string;
setLastChatId: (id: string) => void;
delOneHistoryItem: (e: DeleteChatItemProps) => Promise<any>;
};
export const useChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
lastChatAppId: '',
setLastChatAppId(id: string) {
set((state) => {
state.lastChatAppId = id;
});
},
lastChatId: '',
setLastChatId(id: string) {
set((state) => {
state.lastChatId = id;
});
},
histories: [],
async loadHistories(e) {
const data = await getChatHistories(e);
set((state) => {
state.histories = data;
});
return null;
},
async delOneHistory(props) {
set((state) => {
state.histories = state.histories.filter((item) => item.chatId !== props.chatId);
});
await delChatHistoryById(props);
},
async clearHistories(data) {
set((state) => {
state.histories = [];
});
await clearChatHistoryByAppId(data);
},
pushHistory(history) {
set((state) => {
state.histories = [history, ...state.histories];
});
},
async updateHistory(props) {
const { chatId, customTitle, top, title, updateTime } = props;
const index = get().histories.findIndex((item) => item.chatId === chatId);
if (index > -1) {
const newHistory = {
...get().histories[index],
...(title && { title }),
...(updateTime && { updateTime }),
...(customTitle !== undefined && { customTitle }),
...(top !== undefined && { top })
};
if (customTitle !== undefined || top !== undefined) {
try {
putChatHistory(props);
} catch (error) {}
}
set((state) => {
const newHistories = (() => {
return [
newHistory,
...get().histories.slice(0, index),
...get().histories.slice(index + 1)
];
})();
state.histories = newHistories;
});
}
},
chatData: defaultChatData,
setChatData(e = defaultChatData) {
if (typeof e === 'function') {
set((state) => {
state.chatData = e(state.chatData);
});
} else {
set((state) => {
state.chatData = e;
});
}
},
async delOneHistoryItem(props) {
const { chatId, contentId } = props;
if (!chatId || !contentId) return;
try {
get().setChatData((state) => ({
...state,
history: state.history.filter((item) => item.dataId !== contentId)
}));
await delChatRecordById(props);
} catch (err) {
console.log(err);
}
}
})),
{
name: 'chatStore',
partialize: (state) => ({
lastChatAppId: state.lastChatAppId,
lastChatId: state.lastChatId
})
}
)
)
);

View File

@@ -10,32 +10,16 @@ const nanoid = customAlphabet(
type State = {
localUId: string;
shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
clearLocalHistory: (shareId?: string) => void;
};
export const useShareChatStore = create<State>()(
devtools(
persist(
immer((set, get) => ({
localUId: `shareChat-${Date.now()}-${nanoid()}`,
shareChatHistory: [], // old version field
clearLocalHistory() {
// abandon
set((state) => {
state.shareChatHistory = state.shareChatHistory.map((item) => ({
...item,
delete: true
}));
});
}
localUId: `shareChat-${Date.now()}-${nanoid()}`
})),
{
name: 'shareChatStore',
partialize: (state) => ({
localUId: state.localUId,
shareChatHistory: state.shareChatHistory
})
name: 'shareChatStore'
}
)
)