diff --git a/.github/workflows/build-sandbox-image.yml b/.github/workflows/build-sandbox-image.yml index ae7ccca5b..f84c3785d 100644 --- a/.github/workflows/build-sandbox-image.yml +++ b/.github/workflows/build-sandbox-image.yml @@ -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 }} diff --git a/.github/workflows/fastgpt-image.yml b/.github/workflows/fastgpt-image.yml index 0075aec3d..2752e86cd 100644 --- a/.github/workflows/fastgpt-image.yml +++ b/.github/workflows/fastgpt-image.yml @@ -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}} diff --git a/docSite/content/zh-cn/docs/development/faq.md b/docSite/content/zh-cn/docs/development/faq.md index e1bada463..9bae37d4c 100644 --- a/docSite/content/zh-cn/docs/development/faq.md +++ b/docSite/content/zh-cn/docs/development/faq.md @@ -31,7 +31,9 @@ images: [] ### 页面崩溃 1. 关闭翻译 -2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。(95%情况是配置文件不对,可以F12打开控制台,看具体的空指针情况) +2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。 + * 95%情况是配置文件不对。会提示 xxx undefined + * 提示`URI malformed`,请 Issue 反馈具体操作和页面,这是由于特殊字符串编码解析报错。 3. 某些api不兼容问题(较少) ### 开启内容补全后,响应速度变慢 diff --git a/docSite/content/zh-cn/docs/development/upgrading/485.md b/docSite/content/zh-cn/docs/development/upgrading/485.md index 4c8296f6f..2f1bf0901 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/485.md +++ b/docSite/content/zh-cn/docs/development/upgrading/485.md @@ -36,6 +36,8 @@ curl --location --request POST 'https://{{host}}/api/admin/initv485' \ 3. 优化 - 原文件编码存取 4. 优化 - 文件夹读取,支持单个文件夹超出 100 个文件 5. 优化 - 问答拆分/手动录入,当有`a`字段时,自动将`q`作为补充索引。 -6. 修复 - SSR渲染 -7. 修复 - 定时任务无法实际关闭 -8. 修复 - 输入引导特殊字符导致正则报错 \ No newline at end of file +6. 优化 - 对话框页面代码 +7. 修复 - SSR渲染 +8. 修复 - 定时任务无法实际关闭 +9. 修复 - 输入引导特殊字符导致正则报错 +10. 修复 - 文件包含特殊字符`%`,且为转义时会导致页面崩溃 \ No newline at end of file diff --git a/packages/global/core/dataset/utils.ts b/packages/global/core/dataset/utils.ts index e06cf956e..5f25b230f 100644 --- a/packages/global/core/dataset/utils.ts +++ b/packages/global/core/dataset/utils.ts @@ -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'; } diff --git a/packages/web/components/common/Tabs/RowTabs.tsx b/packages/web/components/common/Tabs/FillRowTabs.tsx similarity index 91% rename from packages/web/components/common/Tabs/RowTabs.tsx rename to packages/web/components/common/Tabs/FillRowTabs.tsx index 3e7bd6125..f55b2f76c 100644 --- a/packages/web/components/common/Tabs/RowTabs.tsx +++ b/packages/web/components/common/Tabs/FillRowTabs.tsx @@ -12,7 +12,7 @@ type Props = Omit & { 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 ( = Omit & { + 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 = ({ + list, + size = 'md', + value, + onChange, + inlineStyles, + ...props +}: Props) => { const { t } = useTranslation(); const sizeMap = useMemo(() => { switch (size) { @@ -49,7 +55,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }: > {list.map((item) => ( { - 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; diff --git a/packages/web/styles/theme.ts b/packages/web/styles/theme.ts index e927db154..472560f41 100644 --- a/packages/web/styles/theme.ts +++ b/packages/web/styles/theme.ts @@ -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' diff --git a/projects/app/src/components/ChatBox/Input/ChatInput.tsx b/projects/app/src/components/ChatBox/Input/ChatInput.tsx index 190b5a535..e7139f6ba 100644 --- a/projects/app/src/components/ChatBox/Input/ChatInput.tsx +++ b/projects/app/src/components/ChatBox/Input/ChatInput.tsx @@ -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; diff --git a/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx b/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx index ee4a7fc98..7a8fc39de 100644 --- a/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx +++ b/projects/app/src/components/ChatBox/components/WholeResponseModal.tsx @@ -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)} ), - id: `${i}` + value: `${i}` })), [response, t] ); @@ -155,7 +154,7 @@ export const ResponseBox = React.memo(function ResponseBox({ <> {!hideTabs && ( - + )} diff --git a/projects/app/src/components/ChatBox/index.tsx b/projects/app/src/components/ChatBox/index.tsx index f0a81df81..86d266883 100644 --- a/projects/app/src/components/ChatBox/index.tsx +++ b/projects/app/src/components/ChatBox/index.tsx @@ -997,7 +997,7 @@ const ChatBox = ( {/* message input */} - {onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && ( + {onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && ( chatController.current?.abort('stop')} diff --git a/projects/app/src/components/Layout/navbar.tsx b/projects/app/src/components/Layout/navbar.tsx index 9bbca008e..b025c4d3d 100644 --- a/projects/app/src/components/Layout/navbar.tsx +++ b/projects/app/src/components/Layout/navbar.tsx @@ -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'; diff --git a/projects/app/src/components/Layout/navbarPhone.tsx b/projects/app/src/components/Layout/navbarPhone.tsx index b3a5f4826..6bfaf6b20 100644 --- a/projects/app/src/components/Layout/navbarPhone.tsx +++ b/projects/app/src/components/Layout/navbarPhone.tsx @@ -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'; diff --git a/projects/app/src/components/SideTabs/index.tsx b/projects/app/src/components/SideTabs/index.tsx index ffb7d4628..fd16c4ccc 100644 --- a/projects/app/src/components/SideTabs/index.tsx +++ b/projects/app/src/components/SideTabs/index.tsx @@ -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 = Omit & { + 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 = ({ + list, + size = 'md', + value, + onChange, + ...props +}: Props) => { const sizeMap = useMemo(() => { switch (size) { case 'sm': @@ -37,14 +42,14 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {list.map((item) => ( bg: 'myGray.100' }} onClick={() => { - if (activeId === item.id) return; - onChange(item.id); + if (value === item.value) return; + onChange(item.value); }} > @@ -71,4 +76,4 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => ); }; -export default React.memo(SideTabs); +export default SideTabs; diff --git a/projects/app/src/components/common/folder/SelectOneResource.tsx b/projects/app/src/components/common/folder/SelectOneResource.tsx index a0f7afc45..3e71a4526 100644 --- a/projects/app/src/components/common/folder/SelectOneResource.tsx +++ b/projects/app/src/components/common/folder/SelectOneResource.tsx @@ -136,7 +136,7 @@ const SelectOneResource = ({ )} - + {item.name} diff --git a/projects/app/src/components/core/app/DatasetParamsModal.tsx b/projects/app/src/components/core/app/DatasetParamsModal.tsx index 79959a35e..997607873 100644 --- a/projects/app/src/components/core/app/DatasetParamsModal.tsx +++ b/projects/app/src/components/core/app/DatasetParamsModal.tsx @@ -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']} > - 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 && ( <> diff --git a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx index cfa7f441d..1426ea1cc 100644 --- a/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx +++ b/projects/app/src/components/support/user/team/TeamManageModal/TeamCard.tsx @@ -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() { - overflow={'auto'} list={Tablist} value={tab} - onChange={(v) => { - setTab(v as TabListEnum); - }} - > + onChange={setTab} + > {/* ctrl buttons */} {tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && ( diff --git a/projects/app/src/global/core/chat/api.d.ts b/projects/app/src/global/core/chat/api.d.ts index 5996cd1ec..35b51bb19 100644 --- a/projects/app/src/global/core/chat/api.d.ts +++ b/projects/app/src/global/core/chat/api.d.ts @@ -51,6 +51,7 @@ export type GetHistoriesProps = OutLinkChatAuthProps & { export type UpdateHistoryProps = OutLinkChatAuthProps & { appId: string; chatId: string; + title?: string; customTitle?: string; top?: boolean; }; diff --git a/projects/app/src/pages/account/index.tsx b/projects/app/src/pages/account/index.tsx index 55b0ba529..52f580478 100644 --- a/projects/app/src/pages/account/index.tsx +++ b/projects/app/src/pages/account/index.tsx @@ -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} > - flex={1} mx={'auto'} mt={2} w={'100%'} list={tabList} - activeId={currentTab} + value={currentTab} onChange={setCurrentTab} /> @@ -157,14 +157,14 @@ const Account = ({ currentTab }: { currentTab: `${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} /> diff --git a/projects/app/src/pages/api/core/chat/updateHistory.ts b/projects/app/src/pages/api/core/chat/updateHistory.ts index 4d98e7d5a..b08767177 100644 --- a/projects/app/src/pages/api/core/chat/updateHistory.ts +++ b/projects/app/src/pages/api/core/chat/updateHistory.ts @@ -8,7 +8,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next'; /* update chat top, custom title */ async function handler(req: ApiRequestProps, 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, res: NextApiRes { appId, chatId }, { updateTime: new Date(), + ...(title !== undefined && { title }), ...(customTitle !== undefined && { customTitle }), ...(top !== undefined && { top }) } diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx index aaabfd3d0..b30e1cb11 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/components/ToolSelectModal.tsx @@ -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 */} - { > - - list={[ - { label: , id: TabEnum.params }, + { label: , value: TabEnum.params }, ...(!['GET', 'DELETE'].includes(requestMethods) ? [ { @@ -322,14 +321,17 @@ export function RenderHttpProps({ {jsonBody?.value && } ), - id: TabEnum.body + value: TabEnum.body } ] : []), - { label: , id: TabEnum.headers } + { + label: , + value: TabEnum.headers + } ]} - activeId={selectedTab} - onChange={(e) => setSelectedTab(e as any)} + value={selectedTab} + onChange={setSelectedTab} /> {params && diff --git a/projects/app/src/pages/app/list/index.tsx b/projects/app/src/pages/app/list/index.tsx index d63de286f..31791e4dc 100644 --- a/projects/app/src/pages/app/list/index.tsx +++ b/projects/app/src/pages/app/list/index.tsx @@ -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'} > - 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 ( 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.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( - () => - !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 ( - {!isPc && appId && ( - 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} /> )} @@ -195,7 +190,11 @@ const ChatHistorySlider = ({ size={'mdSquare'} aria-label={''} borderRadius={'50%'} - onClick={openConfirm(onClearHistory)} + onClick={() => + openConfirm(() => { + onClearHistory(); + })() + } > @@ -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) })} > @@ -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 = ({ )} - + ); }; diff --git a/projects/app/src/pages/chat/components/SliderApps.tsx b/projects/app/src/pages/chat/components/SliderApps.tsx index 280467033..3889f1598 100644 --- a/projects/app/src/pages/chat/components/SliderApps.tsx +++ b/projects/app/src/pages/chat/components/SliderApps.tsx @@ -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 && ( <> - - {t('core.chat.Recent use')} + + {t('core.chat.Recent use')} + {t('common.More')} - - } - > - {({ onClose }) => ( - - { - if (!id) return; - onChangeApp(id); - onClose(); - }} - server={getAppList} - /> - - )} - + } + > + {({ onClose }) => ( + + { + if (!id) return; + onChangeApp(id); + onClose(); + }} + server={getAppList} + /> + + )} + + )} diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 0af076669..efdf3369c 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -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(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(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 ( - + {/* pc show myself apps */} {isPc && ( @@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => { )} - + {/* 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} /> - ); }; +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 ( + + + + ); +}; + export async function getServerSideProps(context: any) { return { props: { @@ -379,4 +321,4 @@ export async function getServerSideProps(context: any) { }; } -export default Chat; +export default Render; diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 6f9be0da9..181509b8a 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -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(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(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 ( <> - - {showHistory === '1' - ? ((children: React.ReactNode) => { - return isPc ? ( - {children} - ) : ( - - - - {children} - - - ); - })( - ({ - id: item.chatId, - title: item.title, - customTitle: item.customTitle, - top: item.top - }))} + + {showHistory === '1' && + ((children: React.ReactNode) => { + return isPc ? ( + {children} + ) : ( + { - 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} + > + + + {children} + + + ); + })( + + 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 */} {/* chat box */} {}} 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 = ({ /> - + ); }; -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 ( + + ; + + ); +}; + +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'])) } }; diff --git a/projects/app/src/pages/chat/team.tsx b/projects/app/src/pages/chat/team.tsx index 191afde1c..9519880b2 100644 --- a/projects/app/src/pages/chat/team.tsx +++ b/projects/app/src/pages/chat/team.tsx @@ -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(null); - const forbidRefresh = useRef(false); - const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure(); + const [chatData, setChatData] = useState(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 ( - - + + {/* pc show myself apps */} {isPc && ( @@ -276,7 +179,7 @@ const OutLink = () => { )} - + {((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 */} { 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 = () => { - + + ); +}; + +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 ( + + + ); }; 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; diff --git a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx index f4d5eb1e1..05b71e6da 100644 --- a/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/InputDataModal.tsx @@ -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'} /> - list={tabList} - activeId={currentTab} - onChange={async (e: any) => { + value={currentTab} + onChange={async (e) => { if (e === TabEnum.delete) { return openConfirm(onDeleteData)(); } diff --git a/projects/app/src/pages/dataset/detail/components/Slider.tsx b/projects/app/src/pages/dataset/detail/components/Slider.tsx index ff50d7750..44371b1a5 100644 --- a/projects/app/src/pages/dataset/detail/components/Slider.tsx +++ b/projects/app/src/pages/dataset/detail/components/Slider.tsx @@ -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 }) => { )} - px={4} flex={1} mx={'auto'} w={'100%'} list={tabList} - activeId={currentTab} - onChange={(e: any) => { - setCurrentTab(e); - }} + value={currentTab} + onChange={setCurrentTab} /> {rebuildingCount > 0 && ( @@ -149,16 +147,13 @@ const Slider = ({ currentTab }: { currentTab: 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} /> )} diff --git a/projects/app/src/pages/login/fastlogin.tsx b/projects/app/src/pages/login/fastlogin.tsx index 2a207c7be..d3c673026 100644 --- a/projects/app/src/pages/login/fastlogin.tsx +++ b/projects/app/src/pages/login/fastlogin.tsx @@ -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'; diff --git a/projects/app/src/pages/login/index.tsx b/projects/app/src/pages/login/index.tsx index a0464b5b0..b6487ae48 100644 --- a/projects/app/src/pages/login/index.tsx +++ b/projects/app/src/pages/login/index.tsx @@ -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'; diff --git a/projects/app/src/pages/login/provider.tsx b/projects/app/src/pages/login/provider.tsx index 2e5cb20d5..5bffa065f 100644 --- a/projects/app/src/pages/login/provider.tsx +++ b/projects/app/src/pages/login/provider.tsx @@ -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'; diff --git a/projects/app/src/web/core/chat/api.ts b/projects/app/src/web/core/chat/api.ts index faf4b4621..46e8ba826 100644 --- a/projects/app/src/web/core/chat/api.ts +++ b/projects/app/src/web/core/chat/api.ts @@ -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); /** diff --git a/projects/app/src/web/core/chat/context/chatContext.tsx b/projects/app/src/web/core/chat/context/chatContext.tsx new file mode 100644 index 000000000..60edc5234 --- /dev/null +++ b/projects/app/src/web/core/chat/context/chatContext.tsx @@ -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; +}; +type ChatContextType = ChatContextValueType & { + chatId: string; + onUpdateHistory: (data: UpdateHistoryProps) => void; + onDelHistory: (data: DelHistoryProps) => Promise; + onClearHistories: (data: ClearHistoriesProps) => Promise; + isOpenSlider: boolean; + onCloseSlider: () => void; + onOpenSlider: () => void; + forbidLoadChat: React.MutableRefObject; + onChangeChatId: (chatId?: string, forbid?: boolean) => void; + onChangeAppId: (appId: string) => void; + isLoading: boolean; +}; + +export const ChatContext = createContext({ + chatId: '', + // forbidLoadChat: undefined, + histories: [], + loadHistories: function (): Promise { + throw new Error('Function not implemented.'); + }, + onUpdateHistory: function (data: UpdateHistoryProps): void { + throw new Error('Function not implemented.'); + }, + onDelHistory: function (data: DelHistoryProps): Promise { + throw new Error('Function not implemented.'); + }, + onClearHistories: function (data: ClearHistoriesProps): Promise { + 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 {children}; +}; + +export default ChatContextProvider; diff --git a/projects/app/src/web/core/chat/context/storeChat.ts b/projects/app/src/web/core/chat/context/storeChat.ts new file mode 100644 index 000000000..f92bbb243 --- /dev/null +++ b/projects/app/src/web/core/chat/context/storeChat.ts @@ -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()( + 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 + }) + } + ) + ) +); diff --git a/projects/app/src/web/core/chat/storeChat.ts b/projects/app/src/web/core/chat/storeChat.ts deleted file mode 100644 index b00aced57..000000000 --- a/projects/app/src/web/core/chat/storeChat.ts +++ /dev/null @@ -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; - delOneHistory(data: DelHistoryProps): Promise; - clearHistories(data: ClearHistoriesProps): Promise; - pushHistory: (history: ChatHistoryItemType) => void; - updateHistory: (e: UpdateHistoryProps & { updateTime?: Date; title?: string }) => Promise; - 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; -}; - -export const useChatStore = create()( - 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 - }) - } - ) - ) -); diff --git a/projects/app/src/web/core/chat/storeShareChat.ts b/projects/app/src/web/core/chat/storeShareChat.ts index 00391408a..e68856c69 100644 --- a/projects/app/src/web/core/chat/storeShareChat.ts +++ b/projects/app/src/web/core/chat/storeShareChat.ts @@ -10,32 +10,16 @@ const nanoid = customAlphabet( type State = { localUId: string; - shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[]; - clearLocalHistory: (shareId?: string) => void; }; export const useShareChatStore = create()( 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' } ) )