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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 908 additions and 1058 deletions

View File

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

View File

@ -11,6 +11,7 @@ jobs:
build-fastgpt-images: build-fastgpt-images:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
# install env
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
@ -31,19 +32,45 @@ jobs:
key: ${{ runner.os }}-buildx-${{ github.sha }} key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-buildx- ${{ runner.os }}-buildx-
# login docker
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }} 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: | run: |
if [[ "${{ github.ref_name }}" == "main" ]]; then 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 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 fi
- name: Build and publish image for main branch or tag push event - name: Build and publish image for main branch or tag push event
env: env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }} DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
@ -56,56 +83,10 @@ jobs:
--push \ --push \
--cache-from=type=local,src=/tmp/.buildx-cache \ --cache-from=type=local,src=/tmp/.buildx-cache \
--cache-to=type=local,dest=/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. 关闭翻译 1. 关闭翻译
2. 检查配置文件是否正常加载如果没有正常加载会导致缺失系统信息在某些操作下会导致空指针。95%情况是配置文件不对可以F12打开控制台看具体的空指针情况 2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。
* 95%情况是配置文件不对。会提示 xxx undefined
* 提示`URI malformed`,请 Issue 反馈具体操作和页面,这是由于特殊字符串编码解析报错。
3. 某些api不兼容问题较少 3. 某些api不兼容问题较少
### 开启内容补全后,响应速度变慢 ### 开启内容补全后,响应速度变慢

View File

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

View File

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

View File

@ -12,7 +12,7 @@ type Props = Omit<BoxProps, 'onChange'> & {
onChange: (e: string) => void; 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 ( return (
<Box <Box
display={'inline-flex'} 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 { Box, Flex, Grid, Image } from '@chakra-ui/react';
import type { FlexProps, GridProps } from '@chakra-ui/react'; import type { FlexProps, GridProps } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '../Icon';
// @ts-ignore type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
interface Props extends GridProps { list: { icon?: string; label: string | React.ReactNode; value: ValueType }[];
list: { id: string; icon?: string; label: string | React.ReactNode }[]; value: ValueType;
activeId: string;
size?: 'sm' | 'md' | 'lg'; size?: 'sm' | 'md' | 'lg';
inlineStyles?: FlexProps; 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 { t } = useTranslation();
const sizeMap = useMemo(() => { const sizeMap = useMemo(() => {
switch (size) { switch (size) {
@ -49,7 +55,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
> >
{list.map((item) => ( {list.map((item) => (
<Flex <Flex
key={item.id} key={item.value as string}
py={sizeMap.inlineP} py={sizeMap.inlineP}
alignItems={'center'} alignItems={'center'}
justifyContent={'center'} justifyContent={'center'}
@ -57,7 +63,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
px={3} px={3}
whiteSpace={'nowrap'} whiteSpace={'nowrap'}
{...inlineStyles} {...inlineStyles}
{...(activeId === item.id {...(value === item.value
? { ? {
color: 'primary.600', color: 'primary.600',
cursor: 'default', cursor: 'default',
@ -68,8 +74,8 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
cursor: 'pointer' cursor: 'pointer'
})} })}
onClick={() => { onClick={() => {
if (activeId === item.id) return; if (value === item.value) return;
onChange(item.id); onChange(item.value);
}} }}
> >
{item.icon && ( {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', color: 'myGray.600',
fontWeight: 'normal', fontWeight: 'normal',
height: '100%', height: '100%',
overflow: 'hidden' overflow: 'hidden',
fontSize: '16px'
}, },
a: { a: {
color: 'primary.600' color: 'primary.600'

View File

@ -364,6 +364,7 @@ const ChatInput = ({
color={'myGray.900'} color={'myGray.900'}
isDisabled={isSpeaking} isDisabled={isSpeaking}
value={inputValue} value={inputValue}
fontSize={['md', 'sm']}
onChange={(e) => { onChange={(e) => {
const textarea = e.target; const textarea = e.target;
textarea.style.height = textareaMinH; 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 { useTranslation } from 'next-i18next';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants'; 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 MyModal from '@fastgpt/web/components/common/MyModal';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import Markdown from '../../Markdown'; import Markdown from '../../Markdown';
import { QuoteList } from './QuoteModal'; import { QuoteList } from './QuoteModal';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants'; import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
@ -142,7 +141,7 @@ export const ResponseBox = React.memo(function ResponseBox({
{t(item.moduleName)} {t(item.moduleName)}
</Flex> </Flex>
), ),
id: `${i}` value: `${i}`
})), })),
[response, t] [response, t]
); );
@ -155,7 +154,7 @@ export const ResponseBox = React.memo(function ResponseBox({
<> <>
{!hideTabs && ( {!hideTabs && (
<Box> <Box>
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} /> <LightRowTabs list={list} value={currentTab} onChange={setCurrentTab} />
</Box> </Box>
)} )}
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}> <Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>

View File

@ -997,7 +997,7 @@ const ChatBox = (
</Box> </Box>
</Box> </Box>
{/* message input */} {/* message input */}
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && ( {onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && (
<ChatInput <ChatInput
onSendMessage={sendPrompt} onSendMessage={sendPrompt}
onStop={() => chatController.current?.abort('stop')} 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 { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore'; 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 { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
import NextLink from 'next/link'; import NextLink from 'next/link';
import Badge from '../Badge'; import Badge from '../Badge';

View File

@ -1,7 +1,7 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { Flex, Box } from '@chakra-ui/react'; 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 { useTranslation } from 'next-i18next';
import Badge from '../Badge'; import Badge from '../Badge';
import MyIcon from '@fastgpt/web/components/common/Icon'; 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 MyIcon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d'; import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d';
// @ts-ignore export type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
export interface Props extends GridProps { list: { value: ValueType; label: string; icon: string }[];
list: { id: string; label: string; icon: string }[]; value: ValueType;
activeId: string;
size?: 'sm' | 'md' | 'lg'; 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(() => { const sizeMap = useMemo(() => {
switch (size) { switch (size) {
case 'sm': case 'sm':
@ -37,14 +42,14 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
<Box fontSize={sizeMap.fontSize} {...props}> <Box fontSize={sizeMap.fontSize} {...props}>
{list.map((item) => ( {list.map((item) => (
<Flex <Flex
key={item.id} key={item.value as string}
py={sizeMap.inlineP} py={sizeMap.inlineP}
borderRadius={'md'} borderRadius={'md'}
px={3} px={3}
mb={2} mb={2}
fontWeight={'medium'} fontWeight={'medium'}
alignItems={'center'} alignItems={'center'}
{...(activeId === item.id {...(value === item.value
? { ? {
bg: ' primary.100 !important', bg: ' primary.100 !important',
color: 'primary.600 ', color: 'primary.600 ',
@ -59,8 +64,8 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
bg: 'myGray.100' bg: 'myGray.100'
}} }}
onClick={() => { onClick={() => {
if (activeId === item.id) return; if (value === item.value) return;
onChange(item.id); onChange(item.value);
}} }}
> >
<MyIcon mr={2} name={item.icon as IconNameType} w={'20px'} /> <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> </Flex>
)} )}
<Avatar ml={index !== 0 ? '0.5rem' : 0} src={item.avatar} w={'1.25rem'} /> <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} {item.name}
</Box> </Box>
</Flex> </Flex>

View File

@ -23,7 +23,7 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants'; import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
import MyRadio from '@/components/common/MyRadio'; import MyRadio from '@/components/common/MyRadio';
import MyIcon from '@fastgpt/web/components/common/Icon'; 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 PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
@ -127,27 +127,27 @@ const DatasetParamsModal = ({
w={['90vw', '550px']} w={['90vw', '550px']}
> >
<ModalBody flex={'auto'} overflow={'auto'}> <ModalBody flex={'auto'} overflow={'auto'}>
<Tabs <LightRowTabs<SearchSettingTabEnum>
mb={3} mb={3}
list={[ list={[
{ {
icon: 'modal/setting', icon: 'modal/setting',
label: t('core.dataset.search.search mode'), label: t('core.dataset.search.search mode'),
id: SearchSettingTabEnum.searchMode value: SearchSettingTabEnum.searchMode
}, },
{ {
icon: 'support/outlink/apikeyFill', icon: 'support/outlink/apikeyFill',
label: t('core.dataset.search.Filter'), label: t('core.dataset.search.Filter'),
id: SearchSettingTabEnum.limit value: SearchSettingTabEnum.limit
}, },
{ {
label: t('core.module.template.Query extension'), label: t('core.module.template.Query extension'),
id: SearchSettingTabEnum.queryExtension, value: SearchSettingTabEnum.queryExtension,
icon: '/imgs/workflow/cfr.svg' icon: '/imgs/workflow/cfr.svg'
} }
]} ]}
activeId={currentTabType} value={currentTabType}
onChange={(e) => setCurrentTabType(e as any)} onChange={setCurrentTabType}
/> />
{currentTabType === SearchSettingTabEnum.searchMode && ( {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 { useUserStore } from '@/web/support/user/useUserStore';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
@ -15,6 +14,7 @@ import { TeamModalContext } from './context';
import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { useRequest } from '@fastgpt/web/hooks/useRequest';
import { delLeaveTeam } from '@/web/support/user/team/api'; import { delLeaveTeam } from '@/web/support/user/team/api';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
enum TabListEnum { enum TabListEnum {
member = 'member', member = 'member',
@ -124,14 +124,12 @@ function TeamCard() {
</Flex> </Flex>
<Flex px={5} alignItems={'center'} justifyContent={'space-between'}> <Flex px={5} alignItems={'center'} justifyContent={'space-between'}>
<RowTabs <LightRowTabs<TabListEnum>
overflow={'auto'} overflow={'auto'}
list={Tablist} list={Tablist}
value={tab} value={tab}
onChange={(v) => { onChange={setTab}
setTab(v as TabListEnum); ></LightRowTabs>
}}
></RowTabs>
{/* ctrl buttons */} {/* ctrl buttons */}
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
{tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && ( {tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && (

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ import {
Switch, Switch,
Textarea Textarea
} from '@chakra-ui/react'; } 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 { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d'; 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 */} {/* Header: row and search */}
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}> <Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
<RowTabs <FillRowTabs
list={[ list={[
{ {
icon: 'core/modules/teamPlugin', 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 { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants'; 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 { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router'; 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'}> <Box pl={'20px'} mb={3} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}> <Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
<RowTabs <FillRowTabs
list={[ list={[
{ {
icon: 'core/modules/basicNode', icon: 'core/modules/basicNode',

View File

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

View File

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

View File

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

View File

@ -7,13 +7,15 @@ import Avatar from '@/components/Avatar';
import { AppListItemType } from '@fastgpt/global/core/app/type'; import { AppListItemType } from '@fastgpt/global/core/app/type';
import MyDivider from '@fastgpt/web/components/common/MyDivider'; import MyDivider from '@fastgpt/web/components/common/MyDivider';
import MyPopover from '@fastgpt/web/components/common/MyPopover/index'; import MyPopover from '@fastgpt/web/components/common/MyPopover/index';
import SelectOneResource from '@/components/common/folder/SelectOneResource';
import { getMyApps } from '@/web/core/app/api'; import { getMyApps } from '@/web/core/app/api';
import { import {
GetResourceFolderListProps, GetResourceFolderListProps,
GetResourceListItemResponse GetResourceListItemResponse
} from '@fastgpt/global/common/parentFolder/type'; } from '@fastgpt/global/common/parentFolder/type';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; 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 SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppId: string }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -74,19 +76,19 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
{!isTeamChat && ( {!isTeamChat && (
<> <>
<MyDivider h={2} my={1} /> <MyDivider h={2} my={1} />
<MyPopover <HStack
placement="right-start" px={4}
offset={[30, -65]} my={2}
trigger="hover" color={'myGray.500'}
Trigger={ fontSize={'sm'}
<HStack justifyContent={'space-between'}
px={4} >
my={2} <Box>{t('core.chat.Recent use')}</Box>
color={'myGray.500'} <MyPopover
fontSize={'sm'} placement="bottom-end"
justifyContent={'space-between'} offset={[20, 10]}
> trigger="hover"
<Box>{t('core.chat.Recent use')}</Box> Trigger={
<HStack <HStack
spacing={0.5} spacing={0.5}
cursor={'pointer'} cursor={'pointer'}
@ -102,23 +104,23 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
<Box>{t('common.More')}</Box> <Box>{t('common.More')}</Box>
<MyIcon name={'common/select'} w={'1rem'} /> <MyIcon name={'common/select'} w={'1rem'} />
</HStack> </HStack>
</HStack> }
} >
> {({ onClose }) => (
{({ onClose }) => ( <Box minH={'200px'}>
<Box minH={'200px'}> <SelectOneResource
<SelectOneResource value={activeAppId}
value={activeAppId} onSelect={(id) => {
onSelect={(id) => { if (!id) return;
if (!id) return; onChangeApp(id);
onChangeApp(id); onClose();
onClose(); }}
}} server={getAppList}
server={getAppList} />
/> </Box>
</Box> )}
)} </MyPopover>
</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 NextHead from '@/components/common/NextHead';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { getInitChatInfo } from '@/web/core/chat/api'; import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
import { import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch'; import { streamFetch } from '@/web/common/api/fetch';
import { useChatStore } from '@/web/core/chat/storeChat'; import { useChatStore } from '@/web/core/chat/context/storeChat';
import { useLoading } from '@fastgpt/web/hooks/useLoading';
import { useToast } from '@fastgpt/web/hooks/useToast'; 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 { useTranslation } from 'next-i18next';
import ChatBox from '@/components/ChatBox'; import ChatBox from '@/components/ChatBox';
@ -29,7 +16,6 @@ import SideBar from '@/components/SideBar';
import ChatHistorySlider from './components/ChatHistorySlider'; import ChatHistorySlider from './components/ChatHistorySlider';
import SliderApps from './components/SliderApps'; import SliderApps from './components/SliderApps';
import ChatHeader from './components/ChatHeader'; import ChatHeader from './components/ChatHeader';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useUserStore } from '@/web/support/user/useUserStore'; import { useUserStore } from '@/web/support/user/useUserStore';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; 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 { getMyApps } from '@/web/core/app/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; 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 router = useRouter();
const theme = useTheme(); const theme = useTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { setLastChatAppId } = useChatStore();
const { const {
lastChatAppId,
setLastChatAppId,
lastChatId,
setLastChatId,
histories,
loadHistories, loadHistories,
pushHistory, onUpdateHistory,
updateHistory, onClearHistories,
delOneHistory, onDelHistory,
clearHistories, isOpenSlider,
chatData, onCloseSlider,
setChatData, forbidLoadChat,
delOneHistoryItem onChangeChatId
} = useChatStore(); } = useContextSelector(ChatContext, (v) => v);
const { userInfo } = useUserStore();
const { userInfo } = useUserStore();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const { Loading, setIsLoading } = useLoading();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const startChat = useCallback( const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => { async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2); const prompts = messages.slice(-2);
const completionChatId = chatId ? chatId : nanoid(); const completionChatId = chatId ? chatId : getNanoid();
const { responseText, responseData } = await streamFetch({ const { responseText, responseData } = await streamFetch({
data: { data: {
@ -89,170 +83,82 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
// new chat // new chat
if (completionChatId !== chatId) { if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = {
chatId: completionChatId,
updateTime: new Date(),
title: newTitle,
appId,
top: false
};
pushHistory(newHistory);
if (controller.signal.reason !== 'leave') { if (controller.signal.reason !== 'leave') {
forbidRefresh.current = true; onChangeChatId(completionChatId, true);
router.replace({ loadHistories();
query: {
chatId: completionChatId,
appId
}
});
} }
} else { } else {
// update chat // update chat
const currentChat = histories.find((item) => item.chatId === chatId); onUpdateHistory({
currentChat && appId,
updateHistory({ chatId: completionChatId,
...currentChat, title: newTitle
updateTime: new Date(), });
title: newTitle
});
} }
// update chat window // update chat window
setChatData((state) => ({ setChatData((state) => ({
...state, ...state,
title: newTitle, title: newTitle
history: ChatBoxRef.current?.getChatHistories() || state.history
})); }));
return { responseText, responseData, isNewChat: forbidRefresh.current }; return { responseText, responseData, isNewChat: forbidLoadChat.current };
}, },
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory] [appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
);
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
() => getMyApps({ getRecentlyChat: true }),
{
manual: false
}
); );
// get chat app info // get chat app info
const loadChatInfo = useCallback( const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
async ({ const { loading } = useRequest2(
appId, async () => {
chatId, if (!appId || forbidLoadChat.current) return;
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
}));
setChatData({ const res = await getInitChatInfo({ appId, chatId });
...res, const history = res.history.map((item) => ({
history ...item,
}); dataId: item.dataId || getNanoid(),
status: ChatStatusEnum.finish
}));
// have records. const result: InitChatResponse = {
ChatBoxRef.current?.resetHistory(history); ...res,
ChatBoxRef.current?.resetVariables(res.variables); history
if (res.history.length > 0) { };
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto'); // reset chat box
}, 500); ChatBoxRef.current?.resetHistory(history);
} ChatBoxRef.current?.resetVariables(res.variables);
} catch (e: any) { if (history.length > 0) {
// reset all chat tore setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
setLastChatAppId(appId);
setChatData(result);
},
{
manual: false,
refreshDeps: [appId, chatId],
onError(e: any) {
setLastChatAppId(''); setLastChatAppId('');
setLastChatId('');
toast({ // reset all chat tore
title: getErrText(e, t('core.chat.Failed to initialize chat')),
status: 'error'
});
if (e?.code === 501) { if (e?.code === 501) {
router.replace('/app/list'); router.replace('/app/list');
} else if (chatId) { } else if (chatId) {
router.replace({ onChangeChatId('');
query: {
...router.query,
chatId: ''
}
});
} }
},
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 ( return (
<Flex h={'100%'}> <Flex h={'100%'}>
<NextHead title={chatData.app.name}></NextHead> <NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */} {/* pc show myself apps */}
{isPc && ( {isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}> <Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
</Box> </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'}> <Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{/* pc always show history. */} {/* pc always show history. */}
{((children: React.ReactNode) => { {((children: React.ReactNode) => {
@ -285,42 +191,17 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appId={appId} appId={appId}
appName={chatData.app.name} appName={chatData.app.name}
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
activeChatId={chatId} onDelHistory={(e) => onDelHistory({ ...e, appId })}
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 })}
onClearHistory={() => { onClearHistory={() => {
clearHistories({ appId }); onClearHistories({ appId });
router.replace({
query: {
appId
}
});
}} }}
onSetHistoryTop={(e) => { onSetHistoryTop={(e) => {
updateHistory({ ...e, appId }); onUpdateHistory({ ...e, appId });
}} }}
onSetCustomTitle={async (e) => { onSetCustomTitle={async (e) => {
updateHistory({ onUpdateHistory({
appId, appId,
chatId: e.chatId, chatId: e.chatId,
title: e.title,
customTitle: e.title customTitle: e.title
}); });
}} }}
@ -340,7 +221,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
appName={chatData.app.name} appName={chatData.app.name}
history={chatData.history} history={chatData.history}
chatModels={chatData.app.chatModels} chatModels={chatData.app.chatModels}
onOpenSlider={onOpenSlider}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)} onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
showHistory showHistory
/> />
@ -356,19 +236,81 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)} showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
feedbackType={'user'} feedbackType={'user'}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })} onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
appId={appId} appId={appId}
chatId={chatId} chatId={chatId}
/> />
</Box> </Box>
</Flex> </Flex>
</Flex> </Flex>
<Loading fixed={false} />
</PageContainer> </PageContainer>
</Flex> </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) { export async function getServerSideProps(context: any) {
return { return {
props: { 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 { 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 { useToast } from '@fastgpt/web/hooks/useToast';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useQuery } from '@tanstack/react-query';
import { streamFetch } from '@/web/common/api/fetch'; import { streamFetch } from '@/web/common/api/fetch';
import { useShareChatStore } from '@/web/core/chat/storeShareChat'; import { useShareChatStore } from '@/web/core/chat/storeShareChat';
import SideBar from '@/components/SideBar'; import SideBar from '@/components/SideBar';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import type { ChatHistoryItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
@ -21,27 +19,30 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useTranslation } from 'next-i18next'; 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 { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; 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 { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type'; import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
import { addLog } from '@fastgpt/service/common/system/log'; import { addLog } from '@fastgpt/service/common/system/log';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import NextHead from '@/components/common/NextHead'; 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 = ({ type Props = {
appName,
appIntro,
appAvatar
}: {
appName: string; appName: string;
appIntro: string; appIntro: string;
appAvatar: string; appAvatar: string;
}) => { shareId: string;
authToken: string;
};
const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { const {
@ -58,28 +59,28 @@ const OutLink = ({
[key: string]: string; [key: string]: string;
}; };
const { toast } = useToast(); const { toast } = useToast();
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const initSign = useRef(false); const initSign = useRef(false);
const [isEmbed, setIdEmbed] = useState(true); const [isEmbed, setIdEmbed] = useState(true);
const { localUId } = useShareChatStore(); const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const {
histories,
loadHistories,
pushHistory,
updateHistory,
delOneHistory,
chatData,
setChatData,
delOneHistoryItem,
clearHistories
} = useChatStore();
const appId = chatData.appId; const appId = chatData.appId;
const { localUId } = useShareChatStore();
const outLinkUid: string = authToken || localUId; const outLinkUid: string = authToken || localUId;
const {
loadHistories,
onUpdateHistory,
onClearHistories,
onDelHistory,
isOpenSlider,
onCloseSlider,
forbidLoadChat,
onChangeChatId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback( const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => { async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const prompts = messages.slice(-2); const prompts = messages.slice(-2);
@ -115,101 +116,86 @@ const OutLink = ({
// new chat // new chat
if (completionChatId !== chatId) { if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = { onChangeChatId(completionChatId, true);
chatId: completionChatId, loadHistories();
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
}
});
}
} else { } else {
// update chat // update chat
const currentChat = histories.find((item) => item.chatId === chatId); onUpdateHistory({
currentChat && appId,
updateHistory({ chatId: completionChatId,
...currentChat, title: newTitle,
updateTime: new Date(), shareId,
title: newTitle, outLinkUid
shareId, });
outLinkUid
});
} }
// update chat window // update chat window
setChatData((state) => ({ setChatData((state) => ({
...state, ...state,
title: newTitle, 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'
})); }));
// hook message
window.top?.postMessage( window.top?.postMessage(
{ {
type: 'shareChatFinish', type: 'shareChatFinish',
data: { data: {
question: result[0]?.value, question: prompts[0]?.content,
answer: responseText answer: responseText
} }
}, },
'*' '*'
); );
return { responseText, responseData, isNewChat: forbidRefresh.current }; return { responseText, responseData, isNewChat: forbidLoadChat.current };
}, },
[ [
chatId, chatId,
customVariables, customVariables,
shareId, shareId,
outLinkUid, outLinkUid,
setChatData, forbidLoadChat,
appId, onChangeChatId,
pushHistory, loadHistories,
router, onUpdateHistory,
histories, appId
updateHistory
] ]
); );
const loadChatInfo = useCallback( const { loading } = useRequest2(
async (shareId: string, chatId: string) => { async () => {
if (!shareId) return null; if (!shareId || !outLinkUid || forbidLoadChat.current) return;
try { const res = await getInitOutLinkChatInfo({
const res = await getInitOutLinkChatInfo({ chatId,
chatId, shareId,
shareId, outLinkUid
outLinkUid });
}); const history = res.history.map((item) => ({
const history = res.history.map((item) => ({ ...item,
...item, dataId: item.dataId || nanoid(),
dataId: item.dataId || nanoid(), status: ChatStatusEnum.finish
status: ChatStatusEnum.finish }));
})); const result: InitChatResponse = {
...res,
history
};
setChatData({ // reset chat box
...res, ChatBoxRef.current?.resetHistory(history);
history ChatBoxRef.current?.resetVariables(res.variables);
}); if (history.length > 0) {
setTimeout(() => {
ChatBoxRef.current?.resetHistory(history); ChatBoxRef.current?.scrollToBottom('auto');
ChatBoxRef.current?.resetVariables(res.variables); }, 500);
}
setChatData(result);
},
{
manual: false,
refreshDeps: [shareId, outLinkUid, chatId],
onSuccess() {
// send init message // send init message
if (!initSign.current) { if (!initSign.current) {
initSign.current = true; initSign.current = true;
@ -217,149 +203,87 @@ const OutLink = ({
window.top?.postMessage({ type: 'shareChatReady' }, '*'); window.top?.postMessage({ type: 'shareChatReady' }, '*');
} }
} }
},
if (chatId && res.history.length > 0) { onError(e: any) {
setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto');
}, 500);
}
} catch (e: any) {
console.log(e); console.log(e);
toast({ toast({
status: 'error', status: 'error',
title: getErrText(e, t('core.shareChat.Init Error')) title: getErrText(e, t('core.shareChat.Init Error'))
}); });
if (chatId) { if (chatId) {
router.replace({ onChangeChatId('');
query: {
...router.query,
chatId: ''
}
});
} }
},
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 // window init
useEffect(() => { useMount(() => {
setIdEmbed(window !== top); setIdEmbed(window !== top);
}, []); });
return ( return (
<> <>
<NextHead title={appName} desc={appIntro} icon={appAvatar} /> <NextHead title={appName} desc={appIntro} icon={appAvatar} />
<PageContainer <PageContainer
isLoading={loading}
{...(isEmbed {...(isEmbed
? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } } ? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } }
: { p: [0, 5] })} : { p: [0, 5] })}
> >
<MyBox <Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
isLoading={isFetching} {showHistory === '1' &&
h={'100%'} ((children: React.ReactNode) => {
display={'flex'} return isPc ? (
flexDirection={['column', 'row']} <SideBar>{children}</SideBar>
bg={'white'} ) : (
> <Drawer
{showHistory === '1' isOpen={isOpenSlider}
? ((children: React.ReactNode) => { placement="left"
return isPc ? ( autoFocus={false}
<SideBar>{children}</SideBar> size={'xs'}
) : (
<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
}))}
onClose={onCloseSlider} onClose={onCloseSlider}
onChangeChat={(chatId) => { >
router.replace({ <DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
query: { <DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
...router.query, {children}
chatId: chatId || '' </DrawerContent>
} </Drawer>
}); );
if (!isPc) { })(
onCloseSlider(); <ChatHistorySlider
} appName={chatData.app.name}
}} appAvatar={chatData.app.avatar}
onDelHistory={({ chatId }) => confirmClearText={t('core.chat.Confirm to clear share chat history')}
delOneHistory({ appId: chatData.appId, chatId, shareId, outLinkUid }) onDelHistory={({ chatId }) =>
} onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
onClearHistory={() => { }
clearHistories({ shareId, outLinkUid }); onClearHistory={() => {
router.replace({ onClearHistories({ shareId, outLinkUid });
query: { }}
...router.query, onSetHistoryTop={(e) => {
chatId: '' onUpdateHistory({
} ...e,
}); appId: chatData.appId,
}} shareId,
onSetHistoryTop={(e) => { outLinkUid
updateHistory({ });
...e, }}
appId: chatData.appId, onSetCustomTitle={(e) => {
shareId, onUpdateHistory({
outLinkUid appId: chatData.appId,
}); chatId: e.chatId,
}} customTitle: e.title,
onSetCustomTitle={async (e) => { shareId,
updateHistory({ outLinkUid
appId: chatData.appId, });
chatId: e.chatId, }}
title: e.title, />
customTitle: e.title, )}
shareId,
outLinkUid
});
}}
/>
)
: null}
{/* chat container */} {/* chat container */}
<Flex <Flex
@ -375,12 +299,10 @@ const OutLink = ({
appName={chatData.app.name} appName={chatData.app.name}
history={chatData.history} history={chatData.history}
showHistory={showHistory === '1'} showHistory={showHistory === '1'}
onOpenSlider={onOpenSlider}
/> />
{/* chat box */} {/* chat box */}
<Box flex={1}> <Box flex={1}>
<ChatBox <ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef} ref={ChatBoxRef}
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar} userAvatar={chatData.userAvatar}
@ -389,8 +311,14 @@ const OutLink = ({
feedbackType={'user'} feedbackType={'user'}
onUpdateVariable={(e) => {}} onUpdateVariable={(e) => {}}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={(e) => onDelMessage={({ contentId }) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, shareId, outLinkUid }) delChatRecordById({
contentId,
appId: chatData.appId,
chatId,
shareId,
outLinkUid
})
} }
appId={chatData.appId} appId={chatData.appId}
chatId={chatId} chatId={chatId}
@ -399,16 +327,37 @@ const OutLink = ({
/> />
</Box> </Box>
</Flex> </Flex>
</MyBox> </Flex>
</PageContainer> </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) { export async function getServerSideProps(context: any) {
const shareId = context?.query?.shareId || ''; const shareId = context?.query?.shareId || '';
const authToken = context?.query?.authToken || '';
const app = await (async () => { const app = await (async () => {
try { try {
@ -433,6 +382,8 @@ export async function getServerSideProps(context: any) {
appName: app?.appId?.name ?? 'name', appName: app?.appId?.name ?? 'name',
appAvatar: app?.appId?.avatar ?? '', appAvatar: app?.appId?.avatar ?? '',
appIntro: app?.appId?.intro ?? 'intro', appIntro: app?.appId?.intro ?? 'intro',
shareId: shareId ?? '',
authToken: authToken ?? '',
...(await serviceSideProps(context, ['file'])) ...(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 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 { useRouter } from 'next/router';
import { import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
Box,
Flex,
useDisclosure,
Drawer,
DrawerOverlay,
DrawerContent,
useTheme
} from '@chakra-ui/react';
import { useToast } from '@fastgpt/web/hooks/useToast'; import { useToast } from '@fastgpt/web/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import SideBar from '@/components/SideBar'; import SideBar from '@/components/SideBar';
import PageContainer from '@/components/PageContainer'; import PageContainer from '@/components/PageContainer';
@ -22,21 +13,26 @@ import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n'; import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils'; import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
import { useChatStore } from '@/web/core/chat/storeChat';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox from '@/components/ChatBox'; import ChatBox from '@/components/ChatBox';
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d'; import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
import { streamFetch } from '@/web/common/api/fetch'; 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 { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants'; import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils'; import { getErrText } from '@fastgpt/global/common/error/utils';
import MyBox from '@fastgpt/web/components/common/MyBox';
import SliderApps from './components/SliderApps'; import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt'; 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 { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const { const {
@ -45,11 +41,7 @@ const OutLink = () => {
chatId = '', chatId = '',
teamToken, teamToken,
...customVariables ...customVariables
} = router.query as { } = router.query as Props & {
teamId: string;
appId: string;
chatId: string;
teamToken: string;
[key: string]: string; [key: string]: string;
}; };
@ -57,22 +49,20 @@ const OutLink = () => {
const theme = useTheme(); const theme = useTheme();
const { isPc } = useSystemStore(); const { isPc } = useSystemStore();
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const forbidRefresh = useRef(false);
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure(); const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
const { const {
chatData,
setChatData,
histories,
loadHistories, loadHistories,
lastChatAppId, onUpdateHistory,
lastChatId, onClearHistories,
pushHistory, onDelHistory,
updateHistory, isOpenSlider,
delOneHistory, onCloseSlider,
delOneHistoryItem, forbidLoadChat,
clearHistories onChangeChatId,
} = useChatStore(); onChangeAppId
} = useContextSelector(ChatContext, (v) => v);
const startChat = useCallback( const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => { async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
@ -99,34 +89,16 @@ const OutLink = () => {
// new chat // new chat
if (completionChatId !== chatId) { if (completionChatId !== chatId) {
const newHistory: ChatHistoryItemType = { onChangeChatId(completionChatId, true);
chatId: completionChatId, loadHistories();
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
}
});
}
} else { } else {
// update chat onUpdateHistory({
const currentChat = histories.find((item) => item.chatId === chatId); appId: chatData.appId,
currentChat && chatId: completionChatId,
updateHistory({ title: newTitle,
...currentChat, teamId,
updateTime: new Date(), teamToken
title: newTitle, });
teamId,
teamToken
});
} }
// update chat window // update chat window
setChatData((state) => ({ setChatData((state) => ({
@ -135,7 +107,7 @@ const OutLink = () => {
history: ChatBoxRef.current?.getChatHistories() || state.history history: ChatBoxRef.current?.getChatHistories() || state.history
})); }));
return { responseText, responseData, isNewChat: forbidRefresh.current }; return { responseText, responseData, isNewChat: forbidLoadChat.current };
}, },
[ [
chatId, chatId,
@ -143,132 +115,63 @@ const OutLink = () => {
appId, appId,
teamId, teamId,
teamToken, teamToken,
setChatData, forbidLoadChat,
pushHistory, onChangeChatId,
router, loadHistories,
histories, onUpdateHistory,
updateHistory 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 // get chat app info
const loadChatInfo = useCallback(async () => { const { loading } = useRequest2(
try { async () => {
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken }); if (!appId || forbidLoadChat.current) return;
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
const history = res.history.map((item) => ({ const history = res.history.map((item) => ({
...item, ...item,
dataId: item.dataId || nanoid(), dataId: item.dataId || nanoid(),
status: ChatStatusEnum.finish status: ChatStatusEnum.finish
})); }));
setChatData({ const result: InitChatResponse = {
...res, ...res,
history history
}); };
// have records. // have records.
ChatBoxRef.current?.resetHistory(history); ChatBoxRef.current?.resetHistory(history);
ChatBoxRef.current?.resetVariables(res.variables); ChatBoxRef.current?.resetVariables(res.variables);
if (res.history.length > 0) { if (res.history.length > 0) {
setTimeout(() => { setTimeout(() => {
ChatBoxRef.current?.scrollToBottom('auto'); ChatBoxRef.current?.scrollToBottom('auto');
}, 500); }, 500);
} }
} catch (e: any) {
toast({ setChatData(result);
title: t('core.chat.Failed to initialize chat'), },
status: 'error' {
}); manual: false,
if (chatId) { refreshDeps: [teamId, teamToken, appId, chatId],
router.replace({ onError(e: any) {
query: { toast({
...router.query, title: getErrText(e, t('core.chat.Failed to initialize chat')),
chatId: '' 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 ( return (
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}> <Flex h={'100%'}>
<NextHead title={chatData.app.name}></NextHead> <NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
{/* pc show myself apps */} {/* pc show myself apps */}
{isPc && ( {isPc && (
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}> <Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
@ -276,7 +179,7 @@ const OutLink = () => {
</Box> </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'}> <Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
{((children: React.ReactNode) => { {((children: React.ReactNode) => {
return isPc || !appId ? ( return isPc || !appId ? (
@ -299,44 +202,18 @@ const OutLink = () => {
apps={myApps} apps={myApps}
appName={chatData.app.name} appName={chatData.app.name}
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
activeChatId={chatId}
confirmClearText={t('core.chat.Confirm to clear history')} confirmClearText={t('core.chat.Confirm to clear history')}
onClose={onCloseSlider} onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
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 })}
onClearHistory={() => { onClearHistory={() => {
clearHistories({ appId, teamId, teamToken }); onClearHistories({ appId, teamId, teamToken });
router.replace({
query: {
...router.query,
chatId: ''
}
});
}} }}
onSetHistoryTop={(e) => { onSetHistoryTop={(e) => {
updateHistory({ ...e, teamId, teamToken, appId }); onUpdateHistory({ ...e, teamId, teamToken, appId });
}} }}
onSetCustomTitle={async (e) => { onSetCustomTitle={async (e) => {
updateHistory({ onUpdateHistory({
appId, appId,
chatId: e.chatId, chatId: e.chatId,
title: e.title,
customTitle: e.title, customTitle: e.title,
teamId, teamId,
teamToken teamToken
@ -357,13 +234,11 @@ const OutLink = () => {
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
appName={chatData.app.name} appName={chatData.app.name}
history={chatData.history} history={chatData.history}
onOpenSlider={onOpenSlider}
showHistory showHistory
/> />
{/* chat box */} {/* chat box */}
<Box flex={1}> <Box flex={1}>
<ChatBox <ChatBox
active={!!chatData.app.name}
ref={ChatBoxRef} ref={ChatBoxRef}
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
userAvatar={chatData.userAvatar} userAvatar={chatData.userAvatar}
@ -372,8 +247,8 @@ const OutLink = () => {
feedbackType={'user'} feedbackType={'user'}
onUpdateVariable={(e) => {}} onUpdateVariable={(e) => {}}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={(e) => onDelMessage={({ contentId }) =>
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken }) delChatRecordById({ contentId, appId: chatData.appId, chatId, teamId, teamToken })
} }
appId={chatData.appId} appId={chatData.appId}
chatId={chatId} chatId={chatId}
@ -384,16 +259,73 @@ const OutLink = () => {
</Flex> </Flex>
</Flex> </Flex>
</PageContainer> </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) { export async function getServerSideProps(context: any) {
return { return {
props: { props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
teamId: context?.query?.teamId || '',
teamToken: context?.query?.teamToken || '',
...(await serviceSideProps(context, ['file'])) ...(await serviceSideProps(context, ['file']))
} }
}; };
} }
export default OutLink; export default Render;

View File

@ -75,16 +75,16 @@ const InputDataModal = ({
}); });
const tabList = [ 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 }), label: t('dataset.data.edit.Index', { amount: indexes.length }),
id: TabEnum.index, value: TabEnum.index,
icon: 'kbTest' icon: 'kbTest'
}, },
...(dataId ...(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({ const { ConfirmModal, openConfirm } = useConfirm({
@ -243,10 +243,10 @@ const InputDataModal = ({
mb={6} mb={6}
fontSize={'sm'} fontSize={'sm'}
/> />
<SideTabs <SideTabs<TabEnum>
list={tabList} list={tabList}
activeId={currentTab} value={currentTab}
onChange={async (e: any) => { onChange={async (e) => {
if (e === TabEnum.delete) { if (e === TabEnum.delete) {
return openConfirm(onDeleteData)(); 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 MyIcon from '@fastgpt/web/components/common/Icon';
import SideTabs from '@/components/SideTabs'; import SideTabs from '@/components/SideTabs';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import Tabs from '@/components/Tabs';
import { useContextSelector } from 'use-context-selector'; import { useContextSelector } from 'use-context-selector';
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
export enum TabEnum { export enum TabEnum {
@ -34,12 +34,12 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
const tabList = [ const tabList = [
{ {
label: t('core.dataset.Collection'), label: t('core.dataset.Collection'),
id: TabEnum.collectionCard, value: TabEnum.collectionCard,
icon: 'common/overviewLight' 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 ...(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> </Flex>
)} )}
</Box> </Box>
<SideTabs <SideTabs<TabEnum>
px={4} px={4}
flex={1} flex={1}
mx={'auto'} mx={'auto'}
w={'100%'} w={'100%'}
list={tabList} list={tabList}
activeId={currentTab} value={currentTab}
onChange={(e: any) => { onChange={setCurrentTab}
setCurrentTab(e);
}}
/> />
<Box px={4}> <Box px={4}>
{rebuildingCount > 0 && ( {rebuildingCount > 0 && (
@ -149,16 +147,13 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
</Flex> </Flex>
) : ( ) : (
<Box mb={3}> <Box mb={3}>
<Tabs <LightRowTabs<TabEnum>
m={'auto'} m={'auto'}
w={'260px'} w={'260px'}
size={isPc ? 'md' : 'sm'} size={isPc ? 'md' : 'sm'}
list={tabList.map((item) => ({ list={tabList}
id: item.id, value={currentTab}
label: item.label onChange={setCurrentTab}
}))}
activeId={currentTab}
onChange={(e: any) => setCurrentTab(e)}
/> />
</Box> </Box>
)} )}

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { ResLogin } from '@/global/support/api/userRes.d'; 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 { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth'; import { clearToken, setToken } from '@/web/support/user/auth';
import { postFastLogin } from '@/web/support/user/api'; 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 type { ResLogin } from '@/global/support/api/userRes.d';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useUserStore } from '@/web/support/user/useUserStore'; 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 LoginForm from './components/LoginForm/LoginForm';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { serviceSideProps } from '@/web/common/utils/i18n'; 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 { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d'; 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 { useUserStore } from '@/web/support/user/useUserStore';
import { clearToken, setToken } from '@/web/support/user/auth'; import { clearToken, setToken } from '@/web/support/user/auth';
import { oauthLogin } from '@/web/support/user/api'; 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 * clear all history by appid
*/ */
export const clearChatHistoryByAppId = (data: ClearHistoriesProps) => export const delClearChatHistories = (data: ClearHistoriesProps) =>
DELETE(`/core/chat/clearHistories`, data); 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 = { type State = {
localUId: string; localUId: string;
shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
clearLocalHistory: (shareId?: string) => void;
}; };
export const useShareChatStore = create<State>()( export const useShareChatStore = create<State>()(
devtools( devtools(
persist( persist(
immer((set, get) => ({ immer((set, get) => ({
localUId: `shareChat-${Date.now()}-${nanoid()}`, localUId: `shareChat-${Date.now()}-${nanoid()}`
shareChatHistory: [], // old version field
clearLocalHistory() {
// abandon
set((state) => {
state.shareChatHistory = state.shareChatHistory.map((item) => ({
...item,
delete: true
}));
});
}
})), })),
{ {
name: 'shareChatStore', name: 'shareChatStore'
partialize: (state) => ({
localUId: state.localUId,
shareChatHistory: state.shareChatHistory
})
} }
) )
) )