mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-21 11:43:56 +00:00
4.8.5 test (#1819)
This commit is contained in:
4
.github/workflows/build-sandbox-image.yml
vendored
4
.github/workflows/build-sandbox-image.yml
vendored
@@ -10,6 +10,7 @@ jobs:
|
||||
build-fastgpt-sandbox-images:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
# install env
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -31,6 +32,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
# login docker
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -49,6 +51,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_HUB_NAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
# Set tag
|
||||
- name: Set image name and tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
@@ -66,6 +69,7 @@ jobs:
|
||||
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt-sandbox:latest" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build and publish image for main branch or tag push event
|
||||
env:
|
||||
Git_Tag: ${{ env.Git_Tag }}
|
||||
|
91
.github/workflows/fastgpt-image.yml
vendored
91
.github/workflows/fastgpt-image.yml
vendored
@@ -11,6 +11,7 @@ jobs:
|
||||
build-fastgpt-images:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
# install env
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -31,19 +32,45 @@ jobs:
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
# login docker
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
- name: Login to Ali Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
username: ${{ secrets.ALI_HUB_USERNAME }}
|
||||
password: ${{ secrets.ALI_HUB_PASSWORD }}
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_NAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
|
||||
# Set tag
|
||||
- name: Set image name and tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Git_Tag=ghcr.io/${{ github.repository_owner }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Git_Latest=ghcr.io/${{ github.repository_owner }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Ali_Tag=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Ali_Latest=${{ secrets.ALI_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Tag=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
echo "Docker_Hub_Latest=${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:latest" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build and publish image for main branch or tag push event
|
||||
env:
|
||||
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
|
||||
@@ -56,56 +83,10 @@ jobs:
|
||||
--push \
|
||||
--cache-from=type=local,src=/tmp/.buildx-cache \
|
||||
--cache-to=type=local,dest=/tmp/.buildx-cache \
|
||||
-t ${DOCKER_REPO_TAGGED} \
|
||||
-t ${Git_Tag} \
|
||||
-t ${Git_Latest} \
|
||||
-t ${Ali_Tag} \
|
||||
-t ${Ali_Latest} \
|
||||
-t ${Docker_Hub_Tag} \
|
||||
-t ${Docker_Hub_Latest} \
|
||||
.
|
||||
push-to-docker-hub:
|
||||
needs: build-fastgpt-images
|
||||
runs-on: ubuntu-20.04
|
||||
if: github.repository == 'labring/FastGPT'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_NAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Pull image from GitHub Container Registry
|
||||
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
- name: Tag image with Docker Hub repository name and version tag
|
||||
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
- name: Push image to Docker Hub
|
||||
run: docker push ${{ secrets.DOCKER_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
push-to-ali-hub:
|
||||
needs: build-fastgpt-images
|
||||
if: github.repository == 'labring/FastGPT'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Login to Ali Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
username: ${{ secrets.ALI_HUB_USERNAME }}
|
||||
password: ${{ secrets.ALI_HUB_PASSWORD }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
run: |
|
||||
if [[ "${{ github.ref_name }}" == "main" ]]; then
|
||||
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IMAGE_TAG=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Pull image from GitHub Container Registry
|
||||
run: docker pull ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
- name: Tag image with Docker Hub repository name and version tag
|
||||
run: docker tag ghcr.io/${{ github.repository_owner }}/fastgpt:${{env.IMAGE_TAG}} ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
- name: Push image to Docker Hub
|
||||
run: docker push ${{ secrets.ALI_IMAGE_NAME }}/fastgpt:${{env.IMAGE_TAG}}
|
||||
|
@@ -31,7 +31,9 @@ images: []
|
||||
### 页面崩溃
|
||||
|
||||
1. 关闭翻译
|
||||
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。(95%情况是配置文件不对,可以F12打开控制台,看具体的空指针情况)
|
||||
2. 检查配置文件是否正常加载,如果没有正常加载会导致缺失系统信息,在某些操作下会导致空指针。
|
||||
* 95%情况是配置文件不对。会提示 xxx undefined
|
||||
* 提示`URI malformed`,请 Issue 反馈具体操作和页面,这是由于特殊字符串编码解析报错。
|
||||
3. 某些api不兼容问题(较少)
|
||||
|
||||
### 开启内容补全后,响应速度变慢
|
||||
|
@@ -36,6 +36,8 @@ curl --location --request POST 'https://{{host}}/api/admin/initv485' \
|
||||
3. 优化 - 原文件编码存取
|
||||
4. 优化 - 文件夹读取,支持单个文件夹超出 100 个文件
|
||||
5. 优化 - 问答拆分/手动录入,当有`a`字段时,自动将`q`作为补充索引。
|
||||
6. 修复 - SSR渲染
|
||||
7. 修复 - 定时任务无法实际关闭
|
||||
8. 修复 - 输入引导特殊字符导致正则报错
|
||||
6. 优化 - 对话框页面代码
|
||||
7. 修复 - SSR渲染
|
||||
8. 修复 - 定时任务无法实际关闭
|
||||
9. 修复 - 输入引导特殊字符导致正则报错
|
||||
10. 修复 - 文件包含特殊字符`%`,且为转义时会导致页面崩溃
|
@@ -24,13 +24,15 @@ export function getSourceNameIcon({
|
||||
sourceName: string;
|
||||
sourceId?: string;
|
||||
}) {
|
||||
const fileIcon = getFileIcon(decodeURIComponent(sourceName), '');
|
||||
if (fileIcon) {
|
||||
return fileIcon;
|
||||
}
|
||||
if (strIsLink(sourceId)) {
|
||||
return 'common/linkBlue';
|
||||
}
|
||||
try {
|
||||
const fileIcon = getFileIcon(decodeURIComponent(sourceName.replace(/%/g, '%25')), '');
|
||||
if (fileIcon) {
|
||||
return fileIcon;
|
||||
}
|
||||
if (strIsLink(sourceId)) {
|
||||
return 'common/linkBlue';
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
return 'file/fill/manual';
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ type Props = Omit<BoxProps, 'onChange'> & {
|
||||
onChange: (e: string) => void;
|
||||
};
|
||||
|
||||
const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
|
||||
const FillRowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: Props) => {
|
||||
return (
|
||||
<Box
|
||||
display={'inline-flex'}
|
||||
@@ -55,4 +55,4 @@ const RowTabs = ({ list, value, onChange, py = '7px', px = '12px', ...props }: P
|
||||
);
|
||||
};
|
||||
|
||||
export default RowTabs;
|
||||
export default FillRowTabs;
|
@@ -2,18 +2,24 @@ import React, { useMemo } from 'react';
|
||||
import { Box, Flex, Grid, Image } from '@chakra-ui/react';
|
||||
import type { FlexProps, GridProps } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import MyIcon from '../Icon';
|
||||
|
||||
// @ts-ignore
|
||||
interface Props extends GridProps {
|
||||
list: { id: string; icon?: string; label: string | React.ReactNode }[];
|
||||
activeId: string;
|
||||
type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
|
||||
list: { icon?: string; label: string | React.ReactNode; value: ValueType }[];
|
||||
value: ValueType;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
inlineStyles?: FlexProps;
|
||||
onChange: (id: string) => void;
|
||||
}
|
||||
onChange: (value: ValueType) => void;
|
||||
};
|
||||
|
||||
const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }: Props) => {
|
||||
const LightRowTabs = <ValueType = string,>({
|
||||
list,
|
||||
size = 'md',
|
||||
value,
|
||||
onChange,
|
||||
inlineStyles,
|
||||
...props
|
||||
}: Props<ValueType>) => {
|
||||
const { t } = useTranslation();
|
||||
const sizeMap = useMemo(() => {
|
||||
switch (size) {
|
||||
@@ -49,7 +55,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
|
||||
>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
key={item.value as string}
|
||||
py={sizeMap.inlineP}
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
@@ -57,7 +63,7 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
|
||||
px={3}
|
||||
whiteSpace={'nowrap'}
|
||||
{...inlineStyles}
|
||||
{...(activeId === item.id
|
||||
{...(value === item.value
|
||||
? {
|
||||
color: 'primary.600',
|
||||
cursor: 'default',
|
||||
@@ -68,8 +74,8 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
|
||||
cursor: 'pointer'
|
||||
})}
|
||||
onClick={() => {
|
||||
if (activeId === item.id) return;
|
||||
onChange(item.id);
|
||||
if (value === item.value) return;
|
||||
onChange(item.value);
|
||||
}}
|
||||
>
|
||||
{item.icon && (
|
||||
@@ -88,4 +94,4 @@ const Tabs = ({ list, size = 'md', activeId, onChange, inlineStyles, ...props }:
|
||||
);
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
export default LightRowTabs;
|
@@ -551,7 +551,8 @@ export const theme = extendTheme({
|
||||
color: 'myGray.600',
|
||||
fontWeight: 'normal',
|
||||
height: '100%',
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
fontSize: '16px'
|
||||
},
|
||||
a: {
|
||||
color: 'primary.600'
|
||||
|
@@ -364,6 +364,7 @@ const ChatInput = ({
|
||||
color={'myGray.900'}
|
||||
isDisabled={isSpeaking}
|
||||
value={inputValue}
|
||||
fontSize={['md', 'sm']}
|
||||
onChange={(e) => {
|
||||
const textarea = e.target;
|
||||
textarea.style.height = textareaMinH;
|
||||
|
@@ -4,9 +4,8 @@ import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
|
||||
|
||||
import Tabs from '../../Tabs';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import MyModal from '@fastgpt/web/components/common/MyModal';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import Markdown from '../../Markdown';
|
||||
import { QuoteList } from './QuoteModal';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
@@ -142,7 +141,7 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
{t(item.moduleName)}
|
||||
</Flex>
|
||||
),
|
||||
id: `${i}`
|
||||
value: `${i}`
|
||||
})),
|
||||
[response, t]
|
||||
);
|
||||
@@ -155,7 +154,7 @@ export const ResponseBox = React.memo(function ResponseBox({
|
||||
<>
|
||||
{!hideTabs && (
|
||||
<Box>
|
||||
<Tabs list={list} activeId={currentTab} onChange={setCurrentTab} />
|
||||
<LightRowTabs list={list} value={currentTab} onChange={setCurrentTab} />
|
||||
</Box>
|
||||
)}
|
||||
<Box py={2} px={4} flex={'1 0 0'} overflow={'auto'}>
|
||||
|
@@ -997,7 +997,7 @@ const ChatBox = (
|
||||
</Box>
|
||||
</Box>
|
||||
{/* message input */}
|
||||
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && (
|
||||
{onStartChat && (chatStarted || filterVariableNodes.length === 0) && active && appId && (
|
||||
<ChatInput
|
||||
onSendMessage={sendPrompt}
|
||||
onStop={() => chatController.current?.abort('stop')}
|
||||
|
@@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps, Flex, Link, LinkProps } from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { HUMAN_ICON } from '@fastgpt/global/common/system/constants';
|
||||
import NextLink from 'next/link';
|
||||
import Badge from '../Badge';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Flex, Box } from '@chakra-ui/react';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Badge from '../Badge';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
|
@@ -4,15 +4,20 @@ import type { GridProps } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type.d';
|
||||
|
||||
// @ts-ignore
|
||||
export interface Props extends GridProps {
|
||||
list: { id: string; label: string; icon: string }[];
|
||||
activeId: string;
|
||||
export type Props<ValueType = string> = Omit<GridProps, 'onChange'> & {
|
||||
list: { value: ValueType; label: string; icon: string }[];
|
||||
value: ValueType;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
onChange: (id: string) => void;
|
||||
}
|
||||
onChange: (value: ValueType) => void;
|
||||
};
|
||||
|
||||
const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) => {
|
||||
const SideTabs = <ValueType = string,>({
|
||||
list,
|
||||
size = 'md',
|
||||
value,
|
||||
onChange,
|
||||
...props
|
||||
}: Props<ValueType>) => {
|
||||
const sizeMap = useMemo(() => {
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
@@ -37,14 +42,14 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
|
||||
<Box fontSize={sizeMap.fontSize} {...props}>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
key={item.value as string}
|
||||
py={sizeMap.inlineP}
|
||||
borderRadius={'md'}
|
||||
px={3}
|
||||
mb={2}
|
||||
fontWeight={'medium'}
|
||||
alignItems={'center'}
|
||||
{...(activeId === item.id
|
||||
{...(value === item.value
|
||||
? {
|
||||
bg: ' primary.100 !important',
|
||||
color: 'primary.600 ',
|
||||
@@ -59,8 +64,8 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
|
||||
bg: 'myGray.100'
|
||||
}}
|
||||
onClick={() => {
|
||||
if (activeId === item.id) return;
|
||||
onChange(item.id);
|
||||
if (value === item.value) return;
|
||||
onChange(item.value);
|
||||
}}
|
||||
>
|
||||
<MyIcon mr={2} name={item.icon as IconNameType} w={'20px'} />
|
||||
@@ -71,4 +76,4 @@ const SideTabs = ({ list, size = 'md', activeId, onChange, ...props }: Props) =>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SideTabs);
|
||||
export default SideTabs;
|
||||
|
@@ -136,7 +136,7 @@ const SelectOneResource = ({
|
||||
</Flex>
|
||||
)}
|
||||
<Avatar ml={index !== 0 ? '0.5rem' : 0} src={item.avatar} w={'1.25rem'} />
|
||||
<Box fontSize={'sm'} ml={2}>
|
||||
<Box fontSize={['md', 'sm']} ml={2}>
|
||||
{item.name}
|
||||
</Box>
|
||||
</Flex>
|
||||
|
@@ -23,7 +23,7 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { DatasetSearchModeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
import MyRadio from '@/components/common/MyRadio';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import PromptEditor from '@fastgpt/web/components/common/Textarea/PromptEditor';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
@@ -127,27 +127,27 @@ const DatasetParamsModal = ({
|
||||
w={['90vw', '550px']}
|
||||
>
|
||||
<ModalBody flex={'auto'} overflow={'auto'}>
|
||||
<Tabs
|
||||
<LightRowTabs<SearchSettingTabEnum>
|
||||
mb={3}
|
||||
list={[
|
||||
{
|
||||
icon: 'modal/setting',
|
||||
label: t('core.dataset.search.search mode'),
|
||||
id: SearchSettingTabEnum.searchMode
|
||||
value: SearchSettingTabEnum.searchMode
|
||||
},
|
||||
{
|
||||
icon: 'support/outlink/apikeyFill',
|
||||
label: t('core.dataset.search.Filter'),
|
||||
id: SearchSettingTabEnum.limit
|
||||
value: SearchSettingTabEnum.limit
|
||||
},
|
||||
{
|
||||
label: t('core.module.template.Query extension'),
|
||||
id: SearchSettingTabEnum.queryExtension,
|
||||
value: SearchSettingTabEnum.queryExtension,
|
||||
icon: '/imgs/workflow/cfr.svg'
|
||||
}
|
||||
]}
|
||||
activeId={currentTabType}
|
||||
onChange={(e) => setCurrentTabType(e as any)}
|
||||
value={currentTabType}
|
||||
onChange={setCurrentTabType}
|
||||
/>
|
||||
{currentTabType === SearchSettingTabEnum.searchMode && (
|
||||
<>
|
||||
|
@@ -3,7 +3,6 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant';
|
||||
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
@@ -15,6 +14,7 @@ import { TeamModalContext } from './context';
|
||||
import { useRequest } from '@fastgpt/web/hooks/useRequest';
|
||||
import { delLeaveTeam } from '@/web/support/user/team/api';
|
||||
import dynamic from 'next/dynamic';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
|
||||
enum TabListEnum {
|
||||
member = 'member',
|
||||
@@ -124,14 +124,12 @@ function TeamCard() {
|
||||
</Flex>
|
||||
|
||||
<Flex px={5} alignItems={'center'} justifyContent={'space-between'}>
|
||||
<RowTabs
|
||||
<LightRowTabs<TabListEnum>
|
||||
overflow={'auto'}
|
||||
list={Tablist}
|
||||
value={tab}
|
||||
onChange={(v) => {
|
||||
setTab(v as TabListEnum);
|
||||
}}
|
||||
></RowTabs>
|
||||
onChange={setTab}
|
||||
></LightRowTabs>
|
||||
{/* ctrl buttons */}
|
||||
<Flex alignItems={'center'}>
|
||||
{tab === TabListEnum.member && userInfo?.team.permission.hasManagePer && (
|
||||
|
1
projects/app/src/global/core/chat/api.d.ts
vendored
1
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -51,6 +51,7 @@ export type GetHistoriesProps = OutLinkChatAuthProps & {
|
||||
export type UpdateHistoryProps = OutLinkChatAuthProps & {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
title?: string;
|
||||
customTitle?: string;
|
||||
top?: boolean;
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex, useDisclosure, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Flex, useTheme } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useRouter } from 'next/router';
|
||||
import dynamic from 'next/dynamic';
|
||||
@@ -7,7 +7,7 @@ import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import SideTabs from '@/components/SideTabs';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import UserInfo from './components/Info';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -31,7 +31,7 @@ enum TabEnum {
|
||||
'loginout' = 'loginout'
|
||||
}
|
||||
|
||||
const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
const Account = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, setUserInfo } = useUserStore();
|
||||
const { feConfigs, isPc, systemVersion } = useSystemStore();
|
||||
@@ -40,14 +40,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
{
|
||||
icon: 'support/user/userLight',
|
||||
label: t('user.Personal Information'),
|
||||
id: TabEnum.info
|
||||
value: TabEnum.info
|
||||
},
|
||||
...(feConfigs?.isPlus
|
||||
? [
|
||||
{
|
||||
icon: 'support/usage/usageRecordLight',
|
||||
label: t('user.Usage Record'),
|
||||
id: TabEnum.usage
|
||||
value: TabEnum.usage
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -56,7 +56,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
{
|
||||
icon: 'support/bill/payRecordLight',
|
||||
label: t('support.wallet.Bills'),
|
||||
id: TabEnum.bill
|
||||
value: TabEnum.bill
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -66,7 +66,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
{
|
||||
icon: 'support/account/promotionLight',
|
||||
label: t('user.Promotion Record'),
|
||||
id: TabEnum.promotion
|
||||
value: TabEnum.promotion
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -75,21 +75,21 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
{
|
||||
icon: 'support/outlink/apikeyLight',
|
||||
label: t('user.apikey.key'),
|
||||
id: TabEnum.apikey
|
||||
value: TabEnum.apikey
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
icon: 'support/user/individuation',
|
||||
label: t('support.account.Individuation'),
|
||||
id: TabEnum.individuation
|
||||
value: TabEnum.individuation
|
||||
},
|
||||
...(feConfigs.isPlus
|
||||
? [
|
||||
{
|
||||
icon: 'support/user/informLight',
|
||||
label: t('user.Notice'),
|
||||
id: TabEnum.inform
|
||||
value: TabEnum.inform
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -97,7 +97,7 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
{
|
||||
icon: 'support/account/loginoutLight',
|
||||
label: t('user.Sign Out'),
|
||||
id: TabEnum.loginout
|
||||
value: TabEnum.loginout
|
||||
}
|
||||
];
|
||||
|
||||
@@ -139,13 +139,13 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
flex={'0 0 200px'}
|
||||
borderRight={theme.borders.base}
|
||||
>
|
||||
<SideTabs
|
||||
<SideTabs<TabEnum>
|
||||
flex={1}
|
||||
mx={'auto'}
|
||||
mt={2}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
activeId={currentTab}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
<Flex alignItems={'center'}>
|
||||
@@ -157,14 +157,14 @@ const Account = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
|
||||
</Flex>
|
||||
) : (
|
||||
<Box mb={3}>
|
||||
<Tabs
|
||||
<LightRowTabs<TabEnum>
|
||||
m={'auto'}
|
||||
size={isPc ? 'md' : 'sm'}
|
||||
list={tabList.map((item) => ({
|
||||
id: item.id,
|
||||
value: item.value,
|
||||
label: item.label
|
||||
}))}
|
||||
activeId={currentTab}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
</Box>
|
||||
|
@@ -8,7 +8,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
|
||||
/* update chat top, custom title */
|
||||
async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiResponse) {
|
||||
const { appId, chatId, customTitle, top } = req.body;
|
||||
const { appId, chatId, title, customTitle, top } = req.body;
|
||||
await autChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
@@ -20,6 +20,7 @@ async function handler(req: ApiRequestProps<UpdateHistoryProps>, res: NextApiRes
|
||||
{ appId, chatId },
|
||||
{
|
||||
updateTime: new Date(),
|
||||
...(title !== undefined && { title }),
|
||||
...(customTitle !== undefined && { customTitle }),
|
||||
...(top !== undefined && { top })
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ import {
|
||||
Switch,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
|
||||
import { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/index.d';
|
||||
@@ -97,7 +97,7 @@ const ToolSelectModal = ({ onClose, ...props }: Props & { onClose: () => void })
|
||||
>
|
||||
{/* Header: row and search */}
|
||||
<Box px={[3, 6]} pt={4} display={'flex'} justifyContent={'space-between'} w={'full'}>
|
||||
<RowTabs
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{
|
||||
icon: 'core/modules/teamPlugin',
|
||||
|
@@ -15,7 +15,7 @@ import { getPreviewPluginNode, getSystemPlugTemplates } from '@/web/core/app/api
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { workflowNodeTemplateList } from '@fastgpt/web/core/workflow/constants';
|
||||
import RowTabs from '@fastgpt/web/components/common/Tabs/RowTabs';
|
||||
import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useRouter } from 'next/router';
|
||||
@@ -143,7 +143,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
|
||||
>
|
||||
<Box pl={'20px'} mb={3} pr={'10px'} whiteSpace={'nowrap'} overflow={'hidden'}>
|
||||
<Flex flex={'1 0 0'} alignItems={'center'} gap={3}>
|
||||
<RowTabs
|
||||
<FillRowTabs
|
||||
list={[
|
||||
{
|
||||
icon: 'core/modules/basicNode',
|
||||
|
@@ -21,11 +21,10 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import JSONEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
|
||||
import { formatEditorVariablePickerIcon } from '@fastgpt/global/core/workflow/utils';
|
||||
import { EditorVariablePickerType } from '@fastgpt/web/components/common/Textarea/PromptEditor/type';
|
||||
@@ -310,9 +309,9 @@ export function RenderHttpProps({
|
||||
label={t('core.module.http.Props tip', { variable: variableText })}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Tabs
|
||||
<LightRowTabs<TabEnum>
|
||||
list={[
|
||||
{ label: <RenderPropsItem text="Params" num={paramsLength} />, id: TabEnum.params },
|
||||
{ label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params },
|
||||
...(!['GET', 'DELETE'].includes(requestMethods)
|
||||
? [
|
||||
{
|
||||
@@ -322,14 +321,17 @@ export function RenderHttpProps({
|
||||
{jsonBody?.value && <Box ml={1}>✅</Box>}
|
||||
</Flex>
|
||||
),
|
||||
id: TabEnum.body
|
||||
value: TabEnum.body
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{ label: <RenderPropsItem text="Headers" num={headersLength} />, id: TabEnum.headers }
|
||||
{
|
||||
label: <RenderPropsItem text="Headers" num={headersLength} />,
|
||||
value: TabEnum.headers
|
||||
}
|
||||
]}
|
||||
activeId={selectedTab}
|
||||
onChange={(e) => setSelectedTab(e as any)}
|
||||
value={selectedTab}
|
||||
onChange={setSelectedTab}
|
||||
/>
|
||||
<Box bg={'white'} borderRadius={'md'}>
|
||||
{params &&
|
||||
|
@@ -1,8 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Box, Flex, Button, useDisclosure, HStack } from '@chakra-ui/react';
|
||||
import { Box, Flex, Button, useDisclosure } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -32,8 +31,8 @@ import {
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { CreateAppType } from './components/CreateModal';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
|
||||
const CreateModal = dynamic(() => import('./components/CreateModal'));
|
||||
const EditFolderModal = dynamic(
|
||||
@@ -121,26 +120,26 @@ const MyApps = () => {
|
||||
alignItems={'center'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Tabs
|
||||
<LightRowTabs
|
||||
list={[
|
||||
{
|
||||
label: appT('type.All'),
|
||||
id: 'ALL'
|
||||
value: 'ALL'
|
||||
},
|
||||
{
|
||||
label: appT('type.Simple bot'),
|
||||
id: AppTypeEnum.simple
|
||||
value: AppTypeEnum.simple
|
||||
},
|
||||
{
|
||||
label: appT('type.Workflow bot'),
|
||||
id: AppTypeEnum.workflow
|
||||
value: AppTypeEnum.workflow
|
||||
},
|
||||
{
|
||||
label: appT('type.Plugin'),
|
||||
id: AppTypeEnum.plugin
|
||||
value: AppTypeEnum.plugin
|
||||
}
|
||||
]}
|
||||
activeId={appType}
|
||||
value={appType}
|
||||
inlineStyles={{ px: 0.5 }}
|
||||
gap={5}
|
||||
display={'flex'}
|
||||
|
@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import MyTag from '@fastgpt/web/components/common/Tag/index';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
|
||||
const ChatHeader = ({
|
||||
history,
|
||||
@@ -16,8 +18,7 @@ const ChatHeader = ({
|
||||
appAvatar,
|
||||
chatModels,
|
||||
showHistory,
|
||||
onRoute2AppDetail,
|
||||
onOpenSlider
|
||||
onRoute2AppDetail
|
||||
}: {
|
||||
history: ChatItemType[];
|
||||
appName: string;
|
||||
@@ -25,7 +26,6 @@ const ChatHeader = ({
|
||||
chatModels?: string[];
|
||||
showHistory?: boolean;
|
||||
onRoute2AppDetail?: () => void;
|
||||
onOpenSlider: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -36,6 +36,8 @@ const ChatHeader = ({
|
||||
[appName, history, t]
|
||||
);
|
||||
|
||||
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
|
@@ -8,7 +8,7 @@ import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
@@ -20,6 +20,9 @@ import {
|
||||
} from '@fastgpt/global/common/parentFolder/type';
|
||||
import { getMyApps } from '@/web/core/app/api';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
|
||||
type HistoryItemType = {
|
||||
id: string;
|
||||
@@ -38,30 +41,22 @@ const ChatHistorySlider = ({
|
||||
appId,
|
||||
appName,
|
||||
appAvatar,
|
||||
history,
|
||||
apps = [],
|
||||
confirmClearText,
|
||||
activeChatId,
|
||||
onChangeChat,
|
||||
onDelHistory,
|
||||
onClearHistory,
|
||||
onSetHistoryTop,
|
||||
onSetCustomTitle,
|
||||
onClose
|
||||
onSetCustomTitle
|
||||
}: {
|
||||
appId?: string;
|
||||
appName: string;
|
||||
appAvatar: string;
|
||||
history: HistoryItemType[];
|
||||
activeChatId: string;
|
||||
apps?: AppListItemType[];
|
||||
confirmClearText: string;
|
||||
onChangeChat: (chatId?: string) => void;
|
||||
onDelHistory: (e: { chatId: string }) => void;
|
||||
onClearHistory: () => void;
|
||||
onSetHistoryTop?: (e: { chatId: string; top: boolean }) => void;
|
||||
onSetCustomTitle?: (e: { chatId: string; title: string }) => void;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
@@ -73,7 +68,28 @@ const ChatHistorySlider = ({
|
||||
const { isPc } = useSystemStore();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.history);
|
||||
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.history);
|
||||
|
||||
const {
|
||||
histories,
|
||||
onChangeChatId,
|
||||
onChangeAppId,
|
||||
chatId: activeChatId,
|
||||
isLoading
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const concatHistory = useMemo(() => {
|
||||
const formatHistories: HistoryItemType[] = histories.map((item) => ({
|
||||
id: item.chatId,
|
||||
title: item.title,
|
||||
customTitle: item.customTitle,
|
||||
top: item.top
|
||||
}));
|
||||
const newChat: HistoryItemType = { id: activeChatId, title: t('core.chat.New Chat') };
|
||||
const activeChat = histories.find((item) => item.chatId === activeChatId);
|
||||
|
||||
return !activeChat ? [newChat].concat(formatHistories) : formatHistories;
|
||||
}, [activeChatId, histories, t]);
|
||||
|
||||
const showApps = apps?.length > 0;
|
||||
|
||||
@@ -86,15 +102,6 @@ const ChatHistorySlider = ({
|
||||
content: confirmClearText
|
||||
});
|
||||
|
||||
const concatHistory = useMemo<HistoryItemType[]>(
|
||||
() =>
|
||||
!activeChatId
|
||||
? //@ts-ignore
|
||||
[{ id: activeChatId, title: t('core.chat.New Chat') }].concat(history)
|
||||
: history,
|
||||
[activeChatId, history, t]
|
||||
);
|
||||
|
||||
const canRouteToDetail = useMemo(
|
||||
() => appId && userInfo?.team.permission.hasWritePer,
|
||||
[appId, userInfo?.team.permission.hasWritePer]
|
||||
@@ -111,22 +118,10 @@ const ChatHistorySlider = ({
|
||||
);
|
||||
}, []);
|
||||
|
||||
const onChangeApp = useCallback(
|
||||
(appId: string) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: '',
|
||||
appId
|
||||
}
|
||||
});
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
position={'relative'}
|
||||
<MyBox
|
||||
isLoading={isLoading}
|
||||
display={'flex'}
|
||||
flexDirection={'column'}
|
||||
w={'100%'}
|
||||
h={'100%'}
|
||||
@@ -162,27 +157,27 @@ const ChatHistorySlider = ({
|
||||
{/* menu */}
|
||||
<Flex w={'100%'} px={[2, 5]} h={'36px'} my={5} alignItems={'center'}>
|
||||
{!isPc && appId && (
|
||||
<Tabs
|
||||
<LightRowTabs<TabEnum>
|
||||
flex={'1 0 0'}
|
||||
mr={2}
|
||||
list={[
|
||||
{ label: t('core.chat.Recent use'), id: TabEnum.recently },
|
||||
{ label: t('App'), id: TabEnum.app },
|
||||
{ label: t('core.chat.History'), id: TabEnum.history }
|
||||
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
|
||||
{ label: t('App'), value: TabEnum.app },
|
||||
{ label: t('core.chat.History'), value: TabEnum.history }
|
||||
]}
|
||||
activeId={currentTab}
|
||||
onChange={(e) => setCurrentTab(e as `${TabEnum}`)}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
variant={'whitePrimary'}
|
||||
flex={['0', 1]}
|
||||
flex={['0 0 auto', 1]}
|
||||
h={'100%'}
|
||||
color={'primary.600'}
|
||||
borderRadius={'xl'}
|
||||
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
|
||||
overflow={'hidden'}
|
||||
onClick={() => onChangeChat()}
|
||||
onClick={() => onChangeChatId()}
|
||||
>
|
||||
{t('core.chat.New Chat')}
|
||||
</Button>
|
||||
@@ -195,7 +190,11 @@ const ChatHistorySlider = ({
|
||||
size={'mdSquare'}
|
||||
aria-label={''}
|
||||
borderRadius={'50%'}
|
||||
onClick={openConfirm(onClearHistory)}
|
||||
onClick={() =>
|
||||
openConfirm(() => {
|
||||
onClearHistory();
|
||||
})()
|
||||
}
|
||||
>
|
||||
<MyIcon name={'common/clearLight'} w={'16px'} />
|
||||
</IconButton>
|
||||
@@ -232,7 +231,7 @@ const ChatHistorySlider = ({
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
onChangeChat(item.id);
|
||||
onChangeChatId(item.id);
|
||||
}
|
||||
})}
|
||||
>
|
||||
@@ -292,7 +291,7 @@ const ChatHistorySlider = ({
|
||||
onClick: () => {
|
||||
onDelHistory({ chatId: item.id });
|
||||
if (item.id === activeChatId) {
|
||||
onChangeChat();
|
||||
onChangeChatId();
|
||||
}
|
||||
},
|
||||
type: 'danger'
|
||||
@@ -324,10 +323,7 @@ const ChatHistorySlider = ({
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
onClick: () => {
|
||||
onChangeApp(item._id);
|
||||
onClose();
|
||||
}
|
||||
onClick: () => onChangeAppId(item._id)
|
||||
})}
|
||||
>
|
||||
<Avatar src={item.avatar} w={'24px'} />
|
||||
@@ -344,8 +340,7 @@ const ChatHistorySlider = ({
|
||||
value={appId}
|
||||
onSelect={(id) => {
|
||||
if (!id) return;
|
||||
onChangeApp(id);
|
||||
onClose();
|
||||
onChangeAppId(id);
|
||||
}}
|
||||
server={getAppList}
|
||||
/>
|
||||
@@ -377,7 +372,7 @@ const ChatHistorySlider = ({
|
||||
)}
|
||||
<EditTitleModal />
|
||||
<ConfirmModal />
|
||||
</Flex>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -7,13 +7,15 @@ import Avatar from '@/components/Avatar';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import MyDivider from '@fastgpt/web/components/common/MyDivider';
|
||||
import MyPopover from '@fastgpt/web/components/common/MyPopover/index';
|
||||
import SelectOneResource from '@/components/common/folder/SelectOneResource';
|
||||
import { getMyApps } from '@/web/core/app/api';
|
||||
import {
|
||||
GetResourceFolderListProps,
|
||||
GetResourceListItemResponse
|
||||
} from '@fastgpt/global/common/parentFolder/type';
|
||||
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const SelectOneResource = dynamic(() => import('@/components/common/folder/SelectOneResource'));
|
||||
|
||||
const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -74,19 +76,19 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
{!isTeamChat && (
|
||||
<>
|
||||
<MyDivider h={2} my={1} />
|
||||
<MyPopover
|
||||
placement="right-start"
|
||||
offset={[30, -65]}
|
||||
trigger="hover"
|
||||
Trigger={
|
||||
<HStack
|
||||
px={4}
|
||||
my={2}
|
||||
color={'myGray.500'}
|
||||
fontSize={'sm'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Box>{t('core.chat.Recent use')}</Box>
|
||||
<HStack
|
||||
px={4}
|
||||
my={2}
|
||||
color={'myGray.500'}
|
||||
fontSize={'sm'}
|
||||
justifyContent={'space-between'}
|
||||
>
|
||||
<Box>{t('core.chat.Recent use')}</Box>
|
||||
<MyPopover
|
||||
placement="bottom-end"
|
||||
offset={[20, 10]}
|
||||
trigger="hover"
|
||||
Trigger={
|
||||
<HStack
|
||||
spacing={0.5}
|
||||
cursor={'pointer'}
|
||||
@@ -102,23 +104,23 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI
|
||||
<Box>{t('common.More')}</Box>
|
||||
<MyIcon name={'common/select'} w={'1rem'} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box minH={'200px'}>
|
||||
<SelectOneResource
|
||||
value={activeAppId}
|
||||
onSelect={(id) => {
|
||||
if (!id) return;
|
||||
onChangeApp(id);
|
||||
onClose();
|
||||
}}
|
||||
server={getAppList}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
}
|
||||
>
|
||||
{({ onClose }) => (
|
||||
<Box minH={'200px'}>
|
||||
<SelectOneResource
|
||||
value={activeAppId}
|
||||
onSelect={(id) => {
|
||||
if (!id) return;
|
||||
onChangeApp(id);
|
||||
onClose();
|
||||
}}
|
||||
server={getAppList}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</MyPopover>
|
||||
</HStack>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
@@ -1,25 +1,12 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getInitChatInfo } from '@/web/core/chat/api';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
Drawer,
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { delChatRecordById, getChatHistories, getInitChatInfo } from '@/web/core/chat/api';
|
||||
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useLoading } from '@fastgpt/web/hooks/useLoading';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
@@ -29,7 +16,6 @@ import SideBar from '@/components/SideBar';
|
||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
import SliderApps from './components/SliderApps';
|
||||
import ChatHeader from './components/ChatHeader';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
|
||||
@@ -39,40 +25,48 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getMyApps } from '@/web/core/app/api';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
import { useMount } from 'ahooks';
|
||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
|
||||
type Props = { appId: string; chatId: string };
|
||||
|
||||
const Chat = ({
|
||||
appId,
|
||||
chatId,
|
||||
myApps
|
||||
}: Props & {
|
||||
myApps: AppListItemType[];
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const forbidRefresh = useRef(false);
|
||||
|
||||
const { setLastChatAppId } = useChatStore();
|
||||
const {
|
||||
lastChatAppId,
|
||||
setLastChatAppId,
|
||||
lastChatId,
|
||||
setLastChatId,
|
||||
histories,
|
||||
loadHistories,
|
||||
pushHistory,
|
||||
updateHistory,
|
||||
delOneHistory,
|
||||
clearHistories,
|
||||
chatData,
|
||||
setChatData,
|
||||
delOneHistoryItem
|
||||
} = useChatStore();
|
||||
const { userInfo } = useUserStore();
|
||||
onUpdateHistory,
|
||||
onClearHistories,
|
||||
onDelHistory,
|
||||
isOpenSlider,
|
||||
onCloseSlider,
|
||||
forbidLoadChat,
|
||||
onChangeChatId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const { userInfo } = useUserStore();
|
||||
const { isPc } = useSystemStore();
|
||||
const { Loading, setIsLoading } = useLoading();
|
||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
const completionChatId = chatId ? chatId : nanoid();
|
||||
const completionChatId = chatId ? chatId : getNanoid();
|
||||
|
||||
const { responseText, responseData } = await streamFetch({
|
||||
data: {
|
||||
@@ -89,170 +83,82 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
const newHistory: ChatHistoryItemType = {
|
||||
chatId: completionChatId,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
appId,
|
||||
top: false
|
||||
};
|
||||
pushHistory(newHistory);
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
forbidRefresh.current = true;
|
||||
router.replace({
|
||||
query: {
|
||||
chatId: completionChatId,
|
||||
appId
|
||||
}
|
||||
});
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
}
|
||||
} else {
|
||||
// update chat
|
||||
const currentChat = histories.find((item) => item.chatId === chatId);
|
||||
currentChat &&
|
||||
updateHistory({
|
||||
...currentChat,
|
||||
updateTime: new Date(),
|
||||
title: newTitle
|
||||
});
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle
|
||||
});
|
||||
}
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
title: newTitle
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidRefresh.current };
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[appId, chatId, histories, pushHistory, router, setChatData, updateHistory]
|
||||
);
|
||||
|
||||
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
|
||||
() => getMyApps({ getRecentlyChat: true }),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
[appId, chatId, forbidLoadChat, loadHistories, onChangeChatId, onUpdateHistory]
|
||||
);
|
||||
|
||||
// get chat app info
|
||||
const loadChatInfo = useCallback(
|
||||
async ({
|
||||
appId,
|
||||
chatId,
|
||||
loading = false
|
||||
}: {
|
||||
appId: string;
|
||||
chatId: string;
|
||||
loading?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
loading && setIsLoading(true);
|
||||
const res = await getInitChatInfo({ appId, chatId });
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
if (!appId || forbidLoadChat.current) return;
|
||||
|
||||
setChatData({
|
||||
...res,
|
||||
history
|
||||
});
|
||||
const res = await getInitChatInfo({ appId, chatId });
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || getNanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
|
||||
// have records.
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
// reset all chat tore
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
};
|
||||
|
||||
// reset chat box
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
setLastChatAppId(appId);
|
||||
setChatData(result);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [appId, chatId],
|
||||
onError(e: any) {
|
||||
setLastChatAppId('');
|
||||
setLastChatId('');
|
||||
toast({
|
||||
title: getErrText(e, t('core.chat.Failed to initialize chat')),
|
||||
status: 'error'
|
||||
});
|
||||
|
||||
// reset all chat tore
|
||||
if (e?.code === 501) {
|
||||
router.replace('/app/list');
|
||||
} else if (chatId) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
onChangeChatId('');
|
||||
}
|
||||
},
|
||||
onFinally() {
|
||||
forbidLoadChat.current = false;
|
||||
}
|
||||
setIsLoading(false);
|
||||
return null;
|
||||
},
|
||||
[setIsLoading, setChatData, setLastChatAppId, setLastChatId, toast, t, router]
|
||||
}
|
||||
);
|
||||
// 初始化聊天框
|
||||
useQuery(['init', { appId, chatId }], () => {
|
||||
// pc: redirect to latest model chat
|
||||
if (!appId && lastChatAppId) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: lastChatAppId,
|
||||
chatId: lastChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!appId && myApps[0]) {
|
||||
return router.replace({
|
||||
query: {
|
||||
appId: myApps[0]._id,
|
||||
chatId: lastChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!appId) {
|
||||
(async () => {
|
||||
const apps = await loadMyApps();
|
||||
if (apps.length === 0) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
router.replace('/app/list');
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
appId: apps[0]._id,
|
||||
chatId: lastChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
return;
|
||||
}
|
||||
|
||||
// store id
|
||||
appId && setLastChatAppId(appId);
|
||||
setLastChatId(chatId);
|
||||
|
||||
if (forbidRefresh.current) {
|
||||
forbidRefresh.current = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return loadChatInfo({
|
||||
appId,
|
||||
chatId,
|
||||
loading: appId !== chatData.appId
|
||||
});
|
||||
});
|
||||
|
||||
useQuery(['loadHistories', appId], () => (appId ? loadHistories({ appId }) : null));
|
||||
|
||||
return (
|
||||
<Flex h={'100%'}>
|
||||
<NextHead title={chatData.app.name}></NextHead>
|
||||
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
|
||||
{/* pc show myself apps */}
|
||||
{isPc && (
|
||||
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
|
||||
@@ -260,7 +166,7 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
{/* pc always show history. */}
|
||||
{((children: React.ReactNode) => {
|
||||
@@ -285,42 +191,17 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
appId={appId}
|
||||
appName={chatData.app.name}
|
||||
appAvatar={chatData.app.avatar}
|
||||
activeChatId={chatId}
|
||||
onClose={onCloseSlider}
|
||||
history={histories.map((item, i) => ({
|
||||
id: item.chatId,
|
||||
title: item.title,
|
||||
customTitle: item.customTitle,
|
||||
top: item.top
|
||||
}))}
|
||||
onChangeChat={(chatId) => {
|
||||
router.replace({
|
||||
query: {
|
||||
chatId: chatId || '',
|
||||
appId
|
||||
}
|
||||
});
|
||||
if (!isPc) {
|
||||
onCloseSlider();
|
||||
}
|
||||
}}
|
||||
onDelHistory={(e) => delOneHistory({ ...e, appId })}
|
||||
onDelHistory={(e) => onDelHistory({ ...e, appId })}
|
||||
onClearHistory={() => {
|
||||
clearHistories({ appId });
|
||||
router.replace({
|
||||
query: {
|
||||
appId
|
||||
}
|
||||
});
|
||||
onClearHistories({ appId });
|
||||
}}
|
||||
onSetHistoryTop={(e) => {
|
||||
updateHistory({ ...e, appId });
|
||||
onUpdateHistory({ ...e, appId });
|
||||
}}
|
||||
onSetCustomTitle={async (e) => {
|
||||
updateHistory({
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: e.chatId,
|
||||
title: e.title,
|
||||
customTitle: e.title
|
||||
});
|
||||
}}
|
||||
@@ -340,7 +221,6 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
chatModels={chatData.app.chatModels}
|
||||
onOpenSlider={onOpenSlider}
|
||||
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
|
||||
showHistory
|
||||
/>
|
||||
@@ -356,19 +236,81 @@ const Chat = ({ appId, chatId }: { appId: string; chatId: string }) => {
|
||||
showFileSelector={checkChatSupportSelectFileByChatModels(chatData.app.chatModels)}
|
||||
feedbackType={'user'}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={(e) => delOneHistoryItem({ ...e, appId, chatId })}
|
||||
onDelMessage={({ contentId }) => delChatRecordById({ contentId, appId, chatId })}
|
||||
appId={appId}
|
||||
chatId={chatId}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Loading fixed={false} />
|
||||
</PageContainer>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: Props) => {
|
||||
const { appId } = props;
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
const { lastChatAppId, lastChatId } = useChatStore();
|
||||
|
||||
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
|
||||
() => getMyApps({ getRecentlyChat: true }),
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data: histories = [], runAsync: loadHistories } = useRequest2(
|
||||
() => (appId ? getChatHistories({ appId }) : Promise.resolve([])),
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [appId]
|
||||
}
|
||||
);
|
||||
|
||||
// 初始化聊天框
|
||||
useMount(async () => {
|
||||
// pc: redirect to latest model chat
|
||||
if (!appId) {
|
||||
if (lastChatAppId) {
|
||||
return router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
appId: lastChatAppId,
|
||||
chatId: lastChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const apps = await loadMyApps();
|
||||
if (apps.length === 0) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
router.replace('/app/list');
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
appId: apps[0]._id,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
|
||||
<Chat {...props} myApps={myApps} />
|
||||
</ChatContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: {
|
||||
@@ -379,4 +321,4 @@ export async function getServerSideProps(context: any) {
|
||||
};
|
||||
}
|
||||
|
||||
export default Chat;
|
||||
export default Render;
|
||||
|
@@ -1,15 +1,13 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
||||
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import { useShareChatStore } from '@/web/core/chat/storeShareChat';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import type { ChatHistoryItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
|
||||
@@ -21,27 +19,30 @@ import ChatHistorySlider from './components/ChatHistorySlider';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getInitOutLinkChatInfo } from '@/web/core/chat/api';
|
||||
import { delChatRecordById, getChatHistories, getInitOutLinkChatInfo } from '@/web/core/chat/api';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { OutLinkWithAppType } from '@fastgpt/global/support/outLink/type';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import Head from 'next/head';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
import { useMount } from 'ahooks';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
|
||||
const OutLink = ({
|
||||
appName,
|
||||
appIntro,
|
||||
appAvatar
|
||||
}: {
|
||||
type Props = {
|
||||
appName: string;
|
||||
appIntro: string;
|
||||
appAvatar: string;
|
||||
}) => {
|
||||
shareId: string;
|
||||
authToken: string;
|
||||
};
|
||||
|
||||
const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const {
|
||||
@@ -58,28 +59,28 @@ const OutLink = ({
|
||||
[key: string]: string;
|
||||
};
|
||||
const { toast } = useToast();
|
||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||
const { isPc } = useSystemStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const forbidRefresh = useRef(false);
|
||||
const initSign = useRef(false);
|
||||
const [isEmbed, setIdEmbed] = useState(true);
|
||||
|
||||
const { localUId } = useShareChatStore();
|
||||
const {
|
||||
histories,
|
||||
loadHistories,
|
||||
pushHistory,
|
||||
updateHistory,
|
||||
delOneHistory,
|
||||
chatData,
|
||||
setChatData,
|
||||
delOneHistoryItem,
|
||||
clearHistories
|
||||
} = useChatStore();
|
||||
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
|
||||
const appId = chatData.appId;
|
||||
|
||||
const { localUId } = useShareChatStore();
|
||||
const outLinkUid: string = authToken || localUId;
|
||||
|
||||
const {
|
||||
loadHistories,
|
||||
onUpdateHistory,
|
||||
onClearHistories,
|
||||
onDelHistory,
|
||||
isOpenSlider,
|
||||
onCloseSlider,
|
||||
forbidLoadChat,
|
||||
onChangeChatId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const prompts = messages.slice(-2);
|
||||
@@ -115,101 +116,86 @@ const OutLink = ({
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
const newHistory: ChatHistoryItemType = {
|
||||
chatId: completionChatId,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
appId,
|
||||
top: false
|
||||
};
|
||||
pushHistory(newHistory);
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
forbidRefresh.current = true;
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: completionChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
} else {
|
||||
// update chat
|
||||
const currentChat = histories.find((item) => item.chatId === chatId);
|
||||
currentChat &&
|
||||
updateHistory({
|
||||
...currentChat,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}
|
||||
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
...state,
|
||||
title: newTitle,
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
}));
|
||||
|
||||
/* post message to report result */
|
||||
const result: ChatSiteItemType[] = GPTMessages2Chats(prompts).map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: 'finish'
|
||||
title: newTitle
|
||||
}));
|
||||
|
||||
// hook message
|
||||
window.top?.postMessage(
|
||||
{
|
||||
type: 'shareChatFinish',
|
||||
data: {
|
||||
question: result[0]?.value,
|
||||
question: prompts[0]?.content,
|
||||
answer: responseText
|
||||
}
|
||||
},
|
||||
'*'
|
||||
);
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidRefresh.current };
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[
|
||||
chatId,
|
||||
customVariables,
|
||||
shareId,
|
||||
outLinkUid,
|
||||
setChatData,
|
||||
appId,
|
||||
pushHistory,
|
||||
router,
|
||||
histories,
|
||||
updateHistory
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
loadHistories,
|
||||
onUpdateHistory,
|
||||
appId
|
||||
]
|
||||
);
|
||||
|
||||
const loadChatInfo = useCallback(
|
||||
async (shareId: string, chatId: string) => {
|
||||
if (!shareId) return null;
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
if (!shareId || !outLinkUid || forbidLoadChat.current) return;
|
||||
|
||||
try {
|
||||
const res = await getInitOutLinkChatInfo({
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
const res = await getInitOutLinkChatInfo({
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
};
|
||||
|
||||
setChatData({
|
||||
...res,
|
||||
history
|
||||
});
|
||||
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
// reset chat box
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
if (history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
setChatData(result);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [shareId, outLinkUid, chatId],
|
||||
onSuccess() {
|
||||
// send init message
|
||||
if (!initSign.current) {
|
||||
initSign.current = true;
|
||||
@@ -217,149 +203,87 @@ const OutLink = ({
|
||||
window.top?.postMessage({ type: 'shareChatReady' }, '*');
|
||||
}
|
||||
}
|
||||
|
||||
if (chatId && res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
},
|
||||
onError(e: any) {
|
||||
console.log(e);
|
||||
toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('core.shareChat.Init Error'))
|
||||
});
|
||||
if (chatId) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
onChangeChatId('');
|
||||
}
|
||||
},
|
||||
onFinally() {
|
||||
forbidLoadChat.current = false;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
[outLinkUid, router, setChatData, t, toast]
|
||||
}
|
||||
);
|
||||
|
||||
const { isFetching } = useQuery(['init', shareId, chatId], () => {
|
||||
if (forbidRefresh.current) {
|
||||
forbidRefresh.current = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
return loadChatInfo(shareId, chatId);
|
||||
});
|
||||
|
||||
// load histories
|
||||
useQuery(['loadHistories', outLinkUid, shareId], () => {
|
||||
if (shareId && outLinkUid) {
|
||||
return loadHistories({
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
// window init
|
||||
useEffect(() => {
|
||||
useMount(() => {
|
||||
setIdEmbed(window !== top);
|
||||
}, []);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<NextHead title={appName} desc={appIntro} icon={appAvatar} />
|
||||
|
||||
<PageContainer
|
||||
isLoading={loading}
|
||||
{...(isEmbed
|
||||
? { p: '0 !important', insertProps: { borderRadius: '0', boxShadow: 'none' } }
|
||||
: { p: [0, 5] })}
|
||||
>
|
||||
<MyBox
|
||||
isLoading={isFetching}
|
||||
h={'100%'}
|
||||
display={'flex'}
|
||||
flexDirection={['column', 'row']}
|
||||
bg={'white'}
|
||||
>
|
||||
{showHistory === '1'
|
||||
? ((children: React.ReactNode) => {
|
||||
return isPc ? (
|
||||
<SideBar>{children}</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
placement="left"
|
||||
autoFocus={false}
|
||||
size={'xs'}
|
||||
onClose={onCloseSlider}
|
||||
>
|
||||
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
|
||||
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
|
||||
{children}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
})(
|
||||
<ChatHistorySlider
|
||||
appName={chatData.app.name}
|
||||
appAvatar={chatData.app.avatar}
|
||||
confirmClearText={t('core.chat.Confirm to clear share chat history')}
|
||||
activeChatId={chatId}
|
||||
history={histories.map((item) => ({
|
||||
id: item.chatId,
|
||||
title: item.title,
|
||||
customTitle: item.customTitle,
|
||||
top: item.top
|
||||
}))}
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
{showHistory === '1' &&
|
||||
((children: React.ReactNode) => {
|
||||
return isPc ? (
|
||||
<SideBar>{children}</SideBar>
|
||||
) : (
|
||||
<Drawer
|
||||
isOpen={isOpenSlider}
|
||||
placement="left"
|
||||
autoFocus={false}
|
||||
size={'xs'}
|
||||
onClose={onCloseSlider}
|
||||
onChangeChat={(chatId) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: chatId || ''
|
||||
}
|
||||
});
|
||||
if (!isPc) {
|
||||
onCloseSlider();
|
||||
}
|
||||
}}
|
||||
onDelHistory={({ chatId }) =>
|
||||
delOneHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
|
||||
}
|
||||
onClearHistory={() => {
|
||||
clearHistories({ shareId, outLinkUid });
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
}}
|
||||
onSetHistoryTop={(e) => {
|
||||
updateHistory({
|
||||
...e,
|
||||
appId: chatData.appId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}}
|
||||
onSetCustomTitle={async (e) => {
|
||||
updateHistory({
|
||||
appId: chatData.appId,
|
||||
chatId: e.chatId,
|
||||
title: e.title,
|
||||
customTitle: e.title,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
>
|
||||
<DrawerOverlay backgroundColor={'rgba(255,255,255,0.5)'} />
|
||||
<DrawerContent maxWidth={'75vw'} boxShadow={'2px 0 10px rgba(0,0,0,0.15)'}>
|
||||
{children}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
);
|
||||
})(
|
||||
<ChatHistorySlider
|
||||
appName={chatData.app.name}
|
||||
appAvatar={chatData.app.avatar}
|
||||
confirmClearText={t('core.chat.Confirm to clear share chat history')}
|
||||
onDelHistory={({ chatId }) =>
|
||||
onDelHistory({ appId: chatData.appId, chatId, shareId, outLinkUid })
|
||||
}
|
||||
onClearHistory={() => {
|
||||
onClearHistories({ shareId, outLinkUid });
|
||||
}}
|
||||
onSetHistoryTop={(e) => {
|
||||
onUpdateHistory({
|
||||
...e,
|
||||
appId: chatData.appId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}}
|
||||
onSetCustomTitle={(e) => {
|
||||
onUpdateHistory({
|
||||
appId: chatData.appId,
|
||||
chatId: e.chatId,
|
||||
customTitle: e.title,
|
||||
shareId,
|
||||
outLinkUid
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* chat container */}
|
||||
<Flex
|
||||
@@ -375,12 +299,10 @@ const OutLink = ({
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
showHistory={showHistory === '1'}
|
||||
onOpenSlider={onOpenSlider}
|
||||
/>
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
active={!!chatData.app.name}
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
@@ -389,8 +311,14 @@ const OutLink = ({
|
||||
feedbackType={'user'}
|
||||
onUpdateVariable={(e) => {}}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={(e) =>
|
||||
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, shareId, outLinkUid })
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({
|
||||
contentId,
|
||||
appId: chatData.appId,
|
||||
chatId,
|
||||
shareId,
|
||||
outLinkUid
|
||||
})
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
@@ -399,16 +327,37 @@ const OutLink = ({
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyBox>
|
||||
</Flex>
|
||||
</PageContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutLink;
|
||||
const Render = (props: Props) => {
|
||||
const { shareId, authToken } = props;
|
||||
const { localUId } = useShareChatStore();
|
||||
const outLinkUid: string = authToken || localUId;
|
||||
|
||||
const { data: histories = [], runAsync: loadHistories } = useRequest2(
|
||||
() => (shareId && outLinkUid ? getChatHistories({ shareId, outLinkUid }) : Promise.resolve([])),
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [shareId, outLinkUid]
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
|
||||
<OutLink {...props} />;
|
||||
</ChatContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default Render;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const shareId = context?.query?.shareId || '';
|
||||
const authToken = context?.query?.authToken || '';
|
||||
|
||||
const app = await (async () => {
|
||||
try {
|
||||
@@ -433,6 +382,8 @@ export async function getServerSideProps(context: any) {
|
||||
appName: app?.appId?.name ?? 'name',
|
||||
appAvatar: app?.appId?.avatar ?? '',
|
||||
appIntro: app?.appId?.intro ?? 'intro',
|
||||
shareId: shareId ?? '',
|
||||
authToken: authToken ?? '',
|
||||
...(await serviceSideProps(context, ['file']))
|
||||
}
|
||||
};
|
||||
|
@@ -1,18 +1,9 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import NextHead from '@/components/common/NextHead';
|
||||
import { getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { delChatRecordById, getChatHistories, getTeamChatInfo } from '@/web/core/chat/api';
|
||||
import { useRouter } from 'next/router';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
Drawer,
|
||||
DrawerOverlay,
|
||||
DrawerContent,
|
||||
useTheme
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, useTheme } from '@chakra-ui/react';
|
||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import SideBar from '@/components/SideBar';
|
||||
import PageContainer from '@/components/PageContainer';
|
||||
@@ -22,21 +13,26 @@ import ChatHeader from './components/ChatHeader';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { checkChatSupportSelectFileByChatModels } from '@/web/core/chat/utils';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
|
||||
import ChatBox from '@/components/ChatBox';
|
||||
import type { ComponentRef, StartChatFnProps } from '@/components/ChatBox/type.d';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import type { ChatHistoryItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
|
||||
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import SliderApps from './components/SliderApps';
|
||||
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext';
|
||||
import { AppListItemType } from '@fastgpt/global/core/app/type';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { InitChatResponse } from '@/global/core/chat/api';
|
||||
import { defaultChatData } from '@/global/core/chat/constants';
|
||||
|
||||
const OutLink = () => {
|
||||
type Props = { appId: string; chatId: string; teamId: string; teamToken: string };
|
||||
|
||||
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const {
|
||||
@@ -45,11 +41,7 @@ const OutLink = () => {
|
||||
chatId = '',
|
||||
teamToken,
|
||||
...customVariables
|
||||
} = router.query as {
|
||||
teamId: string;
|
||||
appId: string;
|
||||
chatId: string;
|
||||
teamToken: string;
|
||||
} = router.query as Props & {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
@@ -57,22 +49,20 @@ const OutLink = () => {
|
||||
const theme = useTheme();
|
||||
const { isPc } = useSystemStore();
|
||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||
const forbidRefresh = useRef(false);
|
||||
|
||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||
const [chatData, setChatData] = useState<InitChatResponse>(defaultChatData);
|
||||
|
||||
const {
|
||||
chatData,
|
||||
setChatData,
|
||||
histories,
|
||||
loadHistories,
|
||||
lastChatAppId,
|
||||
lastChatId,
|
||||
pushHistory,
|
||||
updateHistory,
|
||||
delOneHistory,
|
||||
delOneHistoryItem,
|
||||
clearHistories
|
||||
} = useChatStore();
|
||||
onUpdateHistory,
|
||||
onClearHistories,
|
||||
onDelHistory,
|
||||
isOpenSlider,
|
||||
onCloseSlider,
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
onChangeAppId
|
||||
} = useContextSelector(ChatContext, (v) => v);
|
||||
|
||||
const startChat = useCallback(
|
||||
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
@@ -99,34 +89,16 @@ const OutLink = () => {
|
||||
|
||||
// new chat
|
||||
if (completionChatId !== chatId) {
|
||||
const newHistory: ChatHistoryItemType = {
|
||||
chatId: completionChatId,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
appId,
|
||||
top: false
|
||||
};
|
||||
pushHistory(newHistory);
|
||||
if (controller.signal.reason !== 'leave') {
|
||||
forbidRefresh.current = true;
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: completionChatId
|
||||
}
|
||||
});
|
||||
}
|
||||
onChangeChatId(completionChatId, true);
|
||||
loadHistories();
|
||||
} else {
|
||||
// update chat
|
||||
const currentChat = histories.find((item) => item.chatId === chatId);
|
||||
currentChat &&
|
||||
updateHistory({
|
||||
...currentChat,
|
||||
updateTime: new Date(),
|
||||
title: newTitle,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
onUpdateHistory({
|
||||
appId: chatData.appId,
|
||||
chatId: completionChatId,
|
||||
title: newTitle,
|
||||
teamId,
|
||||
teamToken
|
||||
});
|
||||
}
|
||||
// update chat window
|
||||
setChatData((state) => ({
|
||||
@@ -135,7 +107,7 @@ const OutLink = () => {
|
||||
history: ChatBoxRef.current?.getChatHistories() || state.history
|
||||
}));
|
||||
|
||||
return { responseText, responseData, isNewChat: forbidRefresh.current };
|
||||
return { responseText, responseData, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[
|
||||
chatId,
|
||||
@@ -143,132 +115,63 @@ const OutLink = () => {
|
||||
appId,
|
||||
teamId,
|
||||
teamToken,
|
||||
setChatData,
|
||||
pushHistory,
|
||||
router,
|
||||
histories,
|
||||
updateHistory
|
||||
forbidLoadChat,
|
||||
onChangeChatId,
|
||||
loadHistories,
|
||||
onUpdateHistory,
|
||||
chatData.appId
|
||||
]
|
||||
);
|
||||
|
||||
/* replace router query to last chat */
|
||||
useEffect(() => {
|
||||
if ((!chatId || !appId) && (lastChatId || lastChatAppId)) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: chatId || lastChatId,
|
||||
appId: appId || lastChatAppId
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// get chat app list
|
||||
const loadApps = useCallback(async () => {
|
||||
try {
|
||||
const apps = await getMyTokensApps({ teamId, teamToken });
|
||||
|
||||
if (apps.length <= 0) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('core.chat.You need to a chat app')
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
// if app id not exist, redirect to first app
|
||||
if (!appId || !apps.find((item) => item._id === appId)) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
appId: apps[0]?._id
|
||||
}
|
||||
});
|
||||
}
|
||||
return apps;
|
||||
} catch (error: any) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: getErrText(error)
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}, [appId, teamToken, router, teamId, t, toast]);
|
||||
const { data: myApps = [], isLoading: isLoadingApps } = useQuery(['initApps', teamId], () => {
|
||||
if (!teamId) {
|
||||
toast({
|
||||
status: 'error',
|
||||
title: t('support.user.team.tag.Have not opened')
|
||||
});
|
||||
return;
|
||||
}
|
||||
return loadApps();
|
||||
});
|
||||
|
||||
// load histories
|
||||
useQuery(['loadHistories', appId], () => {
|
||||
if (teamId && appId) {
|
||||
return loadHistories({ teamId, appId, teamToken: teamToken });
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
// get chat app info
|
||||
const loadChatInfo = useCallback(async () => {
|
||||
try {
|
||||
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken: teamToken });
|
||||
const { loading } = useRequest2(
|
||||
async () => {
|
||||
if (!appId || forbidLoadChat.current) return;
|
||||
|
||||
const res = await getTeamChatInfo({ teamId, appId, chatId, teamToken });
|
||||
const history = res.history.map((item) => ({
|
||||
...item,
|
||||
dataId: item.dataId || nanoid(),
|
||||
status: ChatStatusEnum.finish
|
||||
}));
|
||||
|
||||
setChatData({
|
||||
const result: InitChatResponse = {
|
||||
...res,
|
||||
history
|
||||
});
|
||||
};
|
||||
|
||||
// have records.
|
||||
ChatBoxRef.current?.resetHistory(history);
|
||||
ChatBoxRef.current?.resetVariables(res.variables);
|
||||
|
||||
if (res.history.length > 0) {
|
||||
setTimeout(() => {
|
||||
ChatBoxRef.current?.scrollToBottom('auto');
|
||||
}, 500);
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast({
|
||||
title: t('core.chat.Failed to initialize chat'),
|
||||
status: 'error'
|
||||
});
|
||||
if (chatId) {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
|
||||
setChatData(result);
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [teamId, teamToken, appId, chatId],
|
||||
onError(e: any) {
|
||||
toast({
|
||||
title: getErrText(e, t('core.chat.Failed to initialize chat')),
|
||||
status: 'error'
|
||||
});
|
||||
if (chatId) {
|
||||
onChangeChatId('');
|
||||
}
|
||||
},
|
||||
onFinally() {
|
||||
forbidLoadChat.current = false;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}, [teamId, appId, chatId, teamToken, setChatData, toast, t, router]);
|
||||
const { isFetching } = useQuery(['init', teamId, appId, chatId], () => {
|
||||
if (forbidRefresh.current) {
|
||||
forbidRefresh.current = false;
|
||||
return null;
|
||||
}
|
||||
if (teamId && appId) {
|
||||
return loadChatInfo();
|
||||
}
|
||||
return null;
|
||||
});
|
||||
);
|
||||
|
||||
return (
|
||||
<MyBox display={'flex'} h={'100%'} isLoading={isLoadingApps || isFetching}>
|
||||
<NextHead title={chatData.app.name}></NextHead>
|
||||
<Flex h={'100%'}>
|
||||
<NextHead title={chatData.app.name} icon={chatData.app.avatar}></NextHead>
|
||||
{/* pc show myself apps */}
|
||||
{isPc && (
|
||||
<Box borderRight={theme.borders.base} w={'220px'} flexShrink={0}>
|
||||
@@ -276,7 +179,7 @@ const OutLink = () => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<PageContainer flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<PageContainer isLoading={loading} flex={'1 0 0'} w={0} p={[0, '16px']} position={'relative'}>
|
||||
<Flex h={'100%'} flexDirection={['column', 'row']} bg={'white'}>
|
||||
{((children: React.ReactNode) => {
|
||||
return isPc || !appId ? (
|
||||
@@ -299,44 +202,18 @@ const OutLink = () => {
|
||||
apps={myApps}
|
||||
appName={chatData.app.name}
|
||||
appAvatar={chatData.app.avatar}
|
||||
activeChatId={chatId}
|
||||
confirmClearText={t('core.chat.Confirm to clear history')}
|
||||
onClose={onCloseSlider}
|
||||
history={histories.map((item, i) => ({
|
||||
id: item.chatId,
|
||||
title: item.title,
|
||||
customTitle: item.customTitle,
|
||||
top: item.top
|
||||
}))}
|
||||
onChangeChat={(chatId) => {
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: chatId || ''
|
||||
}
|
||||
});
|
||||
if (!isPc) {
|
||||
onCloseSlider();
|
||||
}
|
||||
}}
|
||||
onDelHistory={(e) => delOneHistory({ ...e, appId, teamId, teamToken })}
|
||||
onDelHistory={(e) => onDelHistory({ ...e, appId, teamId, teamToken })}
|
||||
onClearHistory={() => {
|
||||
clearHistories({ appId, teamId, teamToken });
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
onClearHistories({ appId, teamId, teamToken });
|
||||
}}
|
||||
onSetHistoryTop={(e) => {
|
||||
updateHistory({ ...e, teamId, teamToken, appId });
|
||||
onUpdateHistory({ ...e, teamId, teamToken, appId });
|
||||
}}
|
||||
onSetCustomTitle={async (e) => {
|
||||
updateHistory({
|
||||
onUpdateHistory({
|
||||
appId,
|
||||
chatId: e.chatId,
|
||||
title: e.title,
|
||||
customTitle: e.title,
|
||||
teamId,
|
||||
teamToken
|
||||
@@ -357,13 +234,11 @@ const OutLink = () => {
|
||||
appAvatar={chatData.app.avatar}
|
||||
appName={chatData.app.name}
|
||||
history={chatData.history}
|
||||
onOpenSlider={onOpenSlider}
|
||||
showHistory
|
||||
/>
|
||||
{/* chat box */}
|
||||
<Box flex={1}>
|
||||
<ChatBox
|
||||
active={!!chatData.app.name}
|
||||
ref={ChatBoxRef}
|
||||
appAvatar={chatData.app.avatar}
|
||||
userAvatar={chatData.userAvatar}
|
||||
@@ -372,8 +247,8 @@ const OutLink = () => {
|
||||
feedbackType={'user'}
|
||||
onUpdateVariable={(e) => {}}
|
||||
onStartChat={startChat}
|
||||
onDelMessage={(e) =>
|
||||
delOneHistoryItem({ ...e, appId: chatData.appId, chatId, teamId, teamToken })
|
||||
onDelMessage={({ contentId }) =>
|
||||
delChatRecordById({ contentId, appId: chatData.appId, chatId, teamId, teamToken })
|
||||
}
|
||||
appId={chatData.appId}
|
||||
chatId={chatId}
|
||||
@@ -384,16 +259,73 @@ const OutLink = () => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PageContainer>
|
||||
</MyBox>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
const Render = (props: Props) => {
|
||||
const { teamId, appId, teamToken } = props;
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
const { data: myApps = [], runAsync: loadMyApps } = useRequest2(
|
||||
async () => {
|
||||
if (teamId && teamToken) {
|
||||
return getMyTokensApps({ teamId, teamToken });
|
||||
}
|
||||
return [];
|
||||
},
|
||||
{
|
||||
manual: false
|
||||
}
|
||||
);
|
||||
|
||||
const { data: histories = [], runAsync: loadHistories } = useRequest2(
|
||||
async () => {
|
||||
if (teamId && appId && teamToken) {
|
||||
return getChatHistories({ teamId, appId, teamToken: teamToken });
|
||||
}
|
||||
return [];
|
||||
},
|
||||
{
|
||||
manual: false,
|
||||
refreshDeps: [appId, teamId, teamToken]
|
||||
}
|
||||
);
|
||||
|
||||
// 初始化聊天框
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (appId || myApps.length === 0) return;
|
||||
|
||||
router.replace({
|
||||
query: {
|
||||
...router.query,
|
||||
appId: myApps[0]._id,
|
||||
chatId: ''
|
||||
}
|
||||
});
|
||||
})();
|
||||
}, [appId, loadMyApps, myApps, router, t, toast]);
|
||||
|
||||
return (
|
||||
<ChatContextProvider histories={histories} loadHistories={loadHistories}>
|
||||
<Chat {...props} myApps={myApps} />
|
||||
</ChatContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
return {
|
||||
props: {
|
||||
appId: context?.query?.appId || '',
|
||||
chatId: context?.query?.chatId || '',
|
||||
teamId: context?.query?.teamId || '',
|
||||
teamToken: context?.query?.teamToken || '',
|
||||
...(await serviceSideProps(context, ['file']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default OutLink;
|
||||
export default Render;
|
||||
|
@@ -75,16 +75,16 @@ const InputDataModal = ({
|
||||
});
|
||||
|
||||
const tabList = [
|
||||
{ label: t('dataset.data.edit.Content'), id: TabEnum.content, icon: 'common/overviewLight' },
|
||||
{ label: t('dataset.data.edit.Content'), value: TabEnum.content, icon: 'common/overviewLight' },
|
||||
{
|
||||
label: t('dataset.data.edit.Index', { amount: indexes.length }),
|
||||
id: TabEnum.index,
|
||||
value: TabEnum.index,
|
||||
icon: 'kbTest'
|
||||
},
|
||||
...(dataId
|
||||
? [{ label: t('dataset.data.edit.Delete'), id: TabEnum.delete, icon: 'delete' }]
|
||||
? [{ label: t('dataset.data.edit.Delete'), value: TabEnum.delete, icon: 'delete' }]
|
||||
: []),
|
||||
{ label: t('dataset.data.edit.Course'), id: TabEnum.doc, icon: 'common/courseLight' }
|
||||
{ label: t('dataset.data.edit.Course'), value: TabEnum.doc, icon: 'common/courseLight' }
|
||||
];
|
||||
|
||||
const { ConfirmModal, openConfirm } = useConfirm({
|
||||
@@ -243,10 +243,10 @@ const InputDataModal = ({
|
||||
mb={6}
|
||||
fontSize={'sm'}
|
||||
/>
|
||||
<SideTabs
|
||||
<SideTabs<TabEnum>
|
||||
list={tabList}
|
||||
activeId={currentTab}
|
||||
onChange={async (e: any) => {
|
||||
value={currentTab}
|
||||
onChange={async (e) => {
|
||||
if (e === TabEnum.delete) {
|
||||
return openConfirm(onDeleteData)();
|
||||
}
|
||||
|
@@ -8,9 +8,9 @@ import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import SideTabs from '@/components/SideTabs';
|
||||
import { useRouter } from 'next/router';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext';
|
||||
import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs';
|
||||
import { useI18n } from '@/web/context/I18n';
|
||||
|
||||
export enum TabEnum {
|
||||
@@ -34,12 +34,12 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
const tabList = [
|
||||
{
|
||||
label: t('core.dataset.Collection'),
|
||||
id: TabEnum.collectionCard,
|
||||
value: TabEnum.collectionCard,
|
||||
icon: 'common/overviewLight'
|
||||
},
|
||||
{ label: t('core.dataset.test.Search Test'), id: TabEnum.test, icon: 'kbTest' },
|
||||
{ label: t('core.dataset.test.Search Test'), value: TabEnum.test, icon: 'kbTest' },
|
||||
...(datasetDetail.permission.hasManagePer
|
||||
? [{ label: t('common.Config'), id: TabEnum.info, icon: 'common/settingLight' }]
|
||||
? [{ label: t('common.Config'), value: TabEnum.info, icon: 'common/settingLight' }]
|
||||
: [])
|
||||
];
|
||||
|
||||
@@ -78,16 +78,14 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
<SideTabs
|
||||
<SideTabs<TabEnum>
|
||||
px={4}
|
||||
flex={1}
|
||||
mx={'auto'}
|
||||
w={'100%'}
|
||||
list={tabList}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => {
|
||||
setCurrentTab(e);
|
||||
}}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
<Box px={4}>
|
||||
{rebuildingCount > 0 && (
|
||||
@@ -149,16 +147,13 @@ const Slider = ({ currentTab }: { currentTab: TabEnum }) => {
|
||||
</Flex>
|
||||
) : (
|
||||
<Box mb={3}>
|
||||
<Tabs
|
||||
<LightRowTabs<TabEnum>
|
||||
m={'auto'}
|
||||
w={'260px'}
|
||||
size={isPc ? 'md' : 'sm'}
|
||||
list={tabList.map((item) => ({
|
||||
id: item.id,
|
||||
label: item.label
|
||||
}))}
|
||||
activeId={currentTab}
|
||||
onChange={(e: any) => setCurrentTab(e)}
|
||||
list={tabList}
|
||||
value={currentTab}
|
||||
onChange={setCurrentTab}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { postFastLogin } from '@/web/support/user/api';
|
||||
|
@@ -5,7 +5,7 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import LoginForm from './components/LoginForm/LoginForm';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { serviceSideProps } from '@/web/common/utils/i18n';
|
||||
|
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { ResLogin } from '@/global/support/api/userRes.d';
|
||||
import { useChatStore } from '@/web/core/chat/storeChat';
|
||||
import { useChatStore } from '@/web/core/chat/context/storeChat';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { clearToken, setToken } from '@/web/support/user/auth';
|
||||
import { oauthLogin } from '@/web/support/user/api';
|
||||
|
@@ -43,7 +43,7 @@ export const delChatHistoryById = (data: DelHistoryProps) => DELETE(`/core/chat/
|
||||
/**
|
||||
* clear all history by appid
|
||||
*/
|
||||
export const clearChatHistoryByAppId = (data: ClearHistoriesProps) =>
|
||||
export const delClearChatHistories = (data: ClearHistoriesProps) =>
|
||||
DELETE(`/core/chat/clearHistories`, data);
|
||||
|
||||
/**
|
||||
|
151
projects/app/src/web/core/chat/context/chatContext.tsx
Normal file
151
projects/app/src/web/core/chat/context/chatContext.tsx
Normal 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;
|
39
projects/app/src/web/core/chat/context/storeChat.ts
Normal file
39
projects/app/src/web/core/chat/context/storeChat.ts
Normal 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
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
@@ -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
|
||||
})
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
@@ -10,32 +10,16 @@ const nanoid = customAlphabet(
|
||||
|
||||
type State = {
|
||||
localUId: string;
|
||||
shareChatHistory: (ChatHistoryItemType & { delete?: boolean })[];
|
||||
clearLocalHistory: (shareId?: string) => void;
|
||||
};
|
||||
|
||||
export const useShareChatStore = create<State>()(
|
||||
devtools(
|
||||
persist(
|
||||
immer((set, get) => ({
|
||||
localUId: `shareChat-${Date.now()}-${nanoid()}`,
|
||||
shareChatHistory: [], // old version field
|
||||
clearLocalHistory() {
|
||||
// abandon
|
||||
set((state) => {
|
||||
state.shareChatHistory = state.shareChatHistory.map((item) => ({
|
||||
...item,
|
||||
delete: true
|
||||
}));
|
||||
});
|
||||
}
|
||||
localUId: `shareChat-${Date.now()}-${nanoid()}`
|
||||
})),
|
||||
{
|
||||
name: 'shareChatStore',
|
||||
partialize: (state) => ({
|
||||
localUId: state.localUId,
|
||||
shareChatHistory: state.shareChatHistory
|
||||
})
|
||||
name: 'shareChatStore'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
Reference in New Issue
Block a user