From ee2c259c3d41f74a7848bdb20fc8ca544cff172d Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Mon, 22 May 2023 16:47:41 +0800 Subject: [PATCH] perf: text and avatar --- package.json | 5 +- src/api/model.ts | 7 +- src/api/request.ts | 14 ++-- src/components/Avatar/index.tsx | 19 +++++ src/components/Icon/icons/appStore.svg | 1 + src/components/Icon/icons/menu.svg | 1 + .../Icon/icons/phoneTabbar/more.svg | 2 +- src/components/Icon/icons/shareMarket.svg | 1 - src/components/Icon/index.tsx | 5 +- src/components/Layout/navbar.tsx | 17 ++--- src/components/Layout/navbarPhone.tsx | 4 - src/components/Markdown/index.module.scss | 5 +- src/constants/user.ts | 2 +- src/hooks/usePagination.tsx | 1 - src/pages/api/chat/init.ts | 2 +- src/pages/api/model/share/getCollection.ts | 37 ---------- src/pages/api/model/share/getModels.ts | 74 +++++++++++++++---- src/pages/chat/components/Empty.tsx | 11 +-- src/pages/chat/components/ModelList.tsx | 13 +--- src/pages/chat/components/PhoneSliderBar.tsx | 8 +- src/pages/chat/index.tsx | 18 ++--- src/pages/chat/share.tsx | 16 ++-- src/pages/kb/components/Detail.tsx | 18 ++--- src/pages/kb/components/KbList.tsx | 11 +-- src/pages/login/components/RegisterForm.tsx | 2 +- src/pages/model/components/ModelList.tsx | 17 ++--- .../detail/components/ModelEditForm.tsx | 30 ++------ src/pages/model/components/detail/index.tsx | 2 +- src/pages/model/share/components/list.tsx | 43 +++++------ src/pages/model/share/index.tsx | 66 ++++++----------- src/pages/number/index.tsx | 9 +-- src/pages/tools/index.tsx | 4 +- src/service/utils/auth.ts | 43 +++++------ 33 files changed, 231 insertions(+), 277 deletions(-) create mode 100644 src/components/Avatar/index.tsx create mode 100644 src/components/Icon/icons/appStore.svg create mode 100644 src/components/Icon/icons/menu.svg delete mode 100644 src/components/Icon/icons/shareMarket.svg delete mode 100644 src/pages/api/model/share/getCollection.ts diff --git a/package.json b/package.json index c2ccad361..a4daf1211 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fastgpt", - "version": "0.1.0", + "version": "3.7", "private": true, "scripts": { "dev": "next dev", @@ -83,5 +83,8 @@ }, "lint-staged": { "./src/**/*.{ts,tsx,scss}": "npm run format" + }, + "engines": { + "node": ">=18.0.0" } } diff --git a/src/api/model.ts b/src/api/model.ts index a5a3443bd..4015c104a 100644 --- a/src/api/model.ts +++ b/src/api/model.ts @@ -1,6 +1,6 @@ import { GET, POST, DELETE, PUT } from './request'; import type { ModelSchema } from '@/types/mongoSchema'; -import type { ModelUpdateParams, ShareModelItem } from '@/types/model'; +import type { ModelUpdateParams } from '@/types/model'; import { RequestPaging } from '../types/index'; import type { ModelListResponse } from './response/model'; @@ -36,10 +36,7 @@ export const putModelById = (id: string, data: ModelUpdateParams) => */ export const getShareModelList = (data: { searchText?: string } & RequestPaging) => POST(`/model/share/getModels`, data); -/** - * 获取我收藏的模型 - */ -export const getCollectionModels = () => GET(`/model/share/getCollection`); + /** * 收藏/取消收藏模型 */ diff --git a/src/api/request.ts b/src/api/request.ts index c3a4392d1..8f69f80e9 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -54,13 +54,13 @@ function responseError(err: any) { if (typeof err === 'string') { return Promise.reject({ message: err }); } - if (err.response) { - // 有报错响应 - const res = err.response; - if (res.data.code in TOKEN_ERROR_CODE) { - clearCookie(); - return Promise.reject({ message: 'token过期,重新登录' }); - } + // 有报错响应 + if (err?.code in TOKEN_ERROR_CODE) { + clearCookie(); + window.location.replace( + `/login?lastRoute=${encodeURIComponent(location.pathname + location.search)}` + ); + return Promise.reject({ message: 'token过期,重新登录' }); } return Promise.reject(err); } diff --git a/src/components/Avatar/index.tsx b/src/components/Avatar/index.tsx new file mode 100644 index 000000000..0df4ff50d --- /dev/null +++ b/src/components/Avatar/index.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Image } from '@chakra-ui/react'; +import type { ImageProps } from '@chakra-ui/react'; + +const Avatar = ({ w = '30px', ...props }: ImageProps) => { + return ( + + ); +}; + +export default Avatar; diff --git a/src/components/Icon/icons/appStore.svg b/src/components/Icon/icons/appStore.svg new file mode 100644 index 000000000..4dfdd8d23 --- /dev/null +++ b/src/components/Icon/icons/appStore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/icons/menu.svg b/src/components/Icon/icons/menu.svg new file mode 100644 index 000000000..55e8b7b39 --- /dev/null +++ b/src/components/Icon/icons/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Icon/icons/phoneTabbar/more.svg b/src/components/Icon/icons/phoneTabbar/more.svg index b5164ac6a..19608a0f9 100644 --- a/src/components/Icon/icons/phoneTabbar/more.svg +++ b/src/components/Icon/icons/phoneTabbar/more.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/Icon/icons/shareMarket.svg b/src/components/Icon/icons/shareMarket.svg deleted file mode 100644 index de4916b4c..000000000 --- a/src/components/Icon/icons/shareMarket.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 571af2c18..f77884c3b 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -12,7 +12,6 @@ const map = { delete: require('./icons/delete.svg').default, withdraw: require('./icons/withdraw.svg').default, stop: require('./icons/stop.svg').default, - shareMarket: require('./icons/shareMarket.svg').default, collectionLight: require('./icons/collectionLight.svg').default, collectionSolid: require('./icons/collectionSolid.svg').default, chat: require('./icons/chat.svg').default, @@ -27,7 +26,9 @@ const map = { wx: require('./icons/wx.svg').default, out: require('./icons/out.svg').default, git: require('./icons/git.svg').default, - kb: require('./icons/kb.svg').default + kb: require('./icons/kb.svg').default, + appStore: require('./icons/appStore.svg').default, + menu: require('./icons/menu.svg').default }; export type IconName = keyof typeof map; diff --git a/src/components/Layout/navbar.tsx b/src/components/Layout/navbar.tsx index aa3633f32..0e09014e6 100644 --- a/src/components/Layout/navbar.tsx +++ b/src/components/Layout/navbar.tsx @@ -1,9 +1,10 @@ import React, { useMemo } from 'react'; -import { Box, Flex, Image, Tooltip } from '@chakra-ui/react'; +import { Box, Flex, Tooltip } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import MyIcon from '../Icon'; import { useUserStore } from '@/store/user'; import { useChatStore } from '@/store/chat'; +import Avatar from '../Avatar'; export enum NavbarTypeEnum { normal = 'normal', @@ -23,7 +24,7 @@ const Navbar = () => { activeLink: ['/chat'] }, { - label: 'AI助手', + label: '我的应用', icon: 'model', link: `/model?modelId=${lastModelId}`, activeLink: ['/model'] @@ -35,8 +36,8 @@ const Navbar = () => { activeLink: ['/kb'] }, { - label: '共享', - icon: 'shareMarket', + label: '应用市场', + icon: 'appStore', link: '/model/share', activeLink: ['/model/share'] }, @@ -82,13 +83,7 @@ const Navbar = () => { cursor={'pointer'} onClick={() => router.push('/number')} > - + {/* 导航列表 */} diff --git a/src/components/Layout/navbarPhone.tsx b/src/components/Layout/navbarPhone.tsx index 145d0a06b..b1961b27d 100644 --- a/src/components/Layout/navbarPhone.tsx +++ b/src/components/Layout/navbarPhone.tsx @@ -10,25 +10,21 @@ const NavbarPhone = () => { const navbarList = useMemo( () => [ { - label: '聊天', icon: 'tabbarChat', link: `/chat?modelId=${lastChatModelId}&chatId=${lastChatId}`, activeLink: ['/chat'] }, { - label: 'AI助手', icon: 'tabbarModel', link: `/model`, activeLink: ['/model'] }, { - label: '发现', icon: 'tabbarMore', link: '/tools', activeLink: ['/tools'] }, { - label: '我', icon: 'tabbarMe', link: '/number', activeLink: ['/number'] diff --git a/src/components/Markdown/index.module.scss b/src/components/Markdown/index.module.scss index a62ce39c3..9b9c8047e 100644 --- a/src/components/Markdown/index.module.scss +++ b/src/components/Markdown/index.module.scss @@ -339,9 +339,12 @@ text-align: justify; tab-size: 4; word-spacing: normal; - word-break: break-all; width: 100%; + * { + word-break: break-all; + } + p { white-space: pre-line; } diff --git a/src/constants/user.ts b/src/constants/user.ts index a1fd5289d..52c0428fc 100644 --- a/src/constants/user.ts +++ b/src/constants/user.ts @@ -27,6 +27,6 @@ export enum PromotionEnum { export const PromotionTypeMap = { [PromotionEnum.invite]: '好友充值', - [PromotionEnum.shareModel]: 'AI助手分享', + [PromotionEnum.shareModel]: '应用分享', [PromotionEnum.withdraw]: '提现' }; diff --git a/src/hooks/usePagination.tsx b/src/hooks/usePagination.tsx index 6d30ddb30..e188ebebd 100644 --- a/src/hooks/usePagination.tsx +++ b/src/hooks/usePagination.tsx @@ -4,7 +4,6 @@ import { IconButton, Flex, Box, Input } from '@chakra-ui/react'; import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; import { useMutation } from '@tanstack/react-query'; import { useToast } from './useToast'; -import { useQuery } from '@tanstack/react-query'; export const usePagination = ({ api, diff --git a/src/pages/api/chat/init.ts b/src/pages/api/chat/init.ts index 06023d94a..7d68b454e 100644 --- a/src/pages/api/chat/init.ts +++ b/src/pages/api/chat/init.ts @@ -25,7 +25,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) const myModel = await Model.findOne({ userId }); if (!myModel) { const { _id } = await Model.create({ - name: 'AI助手1', + name: '应用1', userId, status: ModelStatusEnum.running }); diff --git a/src/pages/api/model/share/getCollection.ts b/src/pages/api/model/share/getCollection.ts deleted file mode 100644 index 507a8edd1..000000000 --- a/src/pages/api/model/share/getCollection.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { jsonRes } from '@/service/response'; -import { connectToDatabase, Collection } from '@/service/mongo'; -import { authUser } from '@/service/utils/auth'; -import type { ShareModelItem } from '@/types/model'; - -/* 获取模型列表 */ -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - // 凭证校验 - const { userId } = await authUser({ req, authToken: true }); - - await connectToDatabase(); - - // get my collections - const collections = await Collection.find({ - userId - }).populate('modelId', '_id avatar name userId share'); - - jsonRes(res, { - data: collections - .map((item: any) => ({ - _id: item.modelId?._id, - avatar: item.modelId?.avatar || '/icon/logo.png', - name: item.modelId?.name || '', - userId: item.modelId?.userId || '', - share: item.modelId?.share || {}, - isCollection: true - })) - .filter((item) => item.share.isShare) - }); - } catch (err) { - jsonRes(res, { - data: [] - }); - } -} diff --git a/src/pages/api/model/share/getModels.ts b/src/pages/api/model/share/getModels.ts index 8c90699ba..49f35e83b 100644 --- a/src/pages/api/model/share/getModels.ts +++ b/src/pages/api/model/share/getModels.ts @@ -3,6 +3,8 @@ import { jsonRes } from '@/service/response'; import { connectToDatabase, Collection, Model } from '@/service/mongo'; import type { PagingData } from '@/types'; import type { ShareModelItem } from '@/types/model'; +import { parseCookie } from '@/service/utils/auth'; +import { Types } from 'mongoose'; /* 获取模型列表 */ export default async function handler(req: NextApiRequest, res: NextApiResponse) { @@ -15,6 +17,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< await connectToDatabase(); + let userId = ''; + + try { + userId = await parseCookie(req.headers.cookie); + } catch (error) { + error; + } + const regex = new RegExp(searchText, 'i'); const where = { @@ -23,15 +33,58 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< { $or: [{ name: { $regex: regex } }, { 'share.intro': { $regex: regex } }] } ] }; + const pipeline = [ + { + $match: where + }, + { + $lookup: { + from: 'collections', + let: { modelId: '$_id' }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ['$modelId', '$$modelId'] }, + { + $eq: ['$userId', userId ? new Types.ObjectId(userId) : new Types.ObjectId()] + } + ] + } + } + } + ], + as: 'collections' + } + }, + { + $project: { + _id: 1, + avatar: { $ifNull: ['$avatar', '/icon/logo.png'] }, + name: 1, + userId: 1, + share: 1, + isCollection: { + $cond: { if: { $gt: [{ $size: '$collections' }, 0] }, then: true, else: false } + } + } + }, + { + $sort: { 'share.collection': -1 } + }, + { + $skip: (pageNum - 1) * pageSize + }, + { + $limit: pageSize + } + ]; // 获取被分享的模型 const [models, total] = await Promise.all([ - Model.find(where, '_id avatar name userId share') - .sort({ - 'share.collection': -1 - }) - .limit(pageSize) - .skip((pageNum - 1) * pageSize), + // @ts-ignore + Model.aggregate(pipeline), Model.countDocuments(where) ]); @@ -39,14 +92,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< data: { pageNum, pageSize, - data: models.map((item) => ({ - _id: item._id, - avatar: item.avatar || '/icon/logo.png', - name: item.name, - userId: item.userId, - share: item.share, - isCollection: false - })), + data: models, total } }); diff --git a/src/pages/chat/components/Empty.tsx b/src/pages/chat/components/Empty.tsx index b04fce788..e9843e7bd 100644 --- a/src/pages/chat/components/Empty.tsx +++ b/src/pages/chat/components/Empty.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import { Card, Box, Image, Flex } from '@chakra-ui/react'; +import { Card, Box, Flex } from '@chakra-ui/react'; import { useMarkdown } from '@/hooks/useMarkdown'; import Markdown from '@/components/Markdown'; import { LOGO_ICON } from '@/constants/chat'; +import Avatar from '@/components/Avatar'; const Empty = ({ showChatProblem, @@ -31,13 +32,7 @@ const Empty = ({ {name && ( - {''} + {name} diff --git a/src/pages/chat/components/ModelList.tsx b/src/pages/chat/components/ModelList.tsx index 32e86d024..e71108f2b 100644 --- a/src/pages/chat/components/ModelList.tsx +++ b/src/pages/chat/components/ModelList.tsx @@ -1,7 +1,8 @@ import React from 'react'; -import { Box, Flex, Image } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import { useRouter } from 'next/router'; import { ModelListItemType } from '@/types/model'; +import Avatar from '@/components/Avatar'; const ModelList = ({ models, modelId }: { models: ModelListItemType[]; modelId: string }) => { const router = useRouter(); @@ -32,19 +33,13 @@ const ModelList = ({ models, modelId }: { models: ModelListItemType[]; modelId: router.replace(`/chat?modelId=${item._id}`); }} > - + {item.name} - {item.systemPrompt || '这个AI助手没有设置提示词~'} + {item.systemPrompt || '这个 应用 没有设置提示词~'} diff --git a/src/pages/chat/components/PhoneSliderBar.tsx b/src/pages/chat/components/PhoneSliderBar.tsx index 1c42d698a..7b364df8d 100644 --- a/src/pages/chat/components/PhoneSliderBar.tsx +++ b/src/pages/chat/components/PhoneSliderBar.tsx @@ -7,8 +7,7 @@ import { Divider, useDisclosure, useColorMode, - useColorModeValue, - Image + useColorModeValue } from '@chakra-ui/react'; import { useUserStore } from '@/store/user'; import { useQuery } from '@tanstack/react-query'; @@ -17,6 +16,7 @@ import MyIcon from '@/components/Icon'; import WxConcat from '@/components/WxConcat'; import { delChatHistoryById } from '@/api/chat'; import { useChatStore } from '@/store/chat'; +import Avatar from '@/components/Avatar'; const PhoneSliderBar = ({ chatId, @@ -74,7 +74,7 @@ const PhoneSliderBar = ({ color={'white'} > - AI助手 + AI应用 {/* 新对话 */} )} @@ -621,13 +613,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`; }} > - + {item.name} diff --git a/src/pages/model/components/detail/index.tsx b/src/pages/model/components/detail/index.tsx index 3f5a21bf4..c24e3f498 100644 --- a/src/pages/model/components/detail/index.tsx +++ b/src/pages/model/components/detail/index.tsx @@ -29,7 +29,7 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => { }, onError(err: any) { toast({ - title: err?.message || '获取AI助手异常', + title: err?.message || '获取应用异常', status: 'error' }); setLastModelId(''); diff --git a/src/pages/model/share/components/list.tsx b/src/pages/model/share/components/list.tsx index 16d9c8515..a0bfa71cb 100644 --- a/src/pages/model/share/components/list.tsx +++ b/src/pages/model/share/components/list.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { Box, Flex, Image, Button } from '@chakra-ui/react'; +import { Box, Flex, Button, Tooltip } from '@chakra-ui/react'; import type { ShareModelItem } from '@/types/model'; import { useRouter } from 'next/router'; import MyIcon from '@/components/Icon'; import styles from '../index.module.scss'; +import Avatar from '@/components/Avatar'; const ShareModelList = ({ models = [], @@ -27,27 +28,29 @@ const ShareModelList = ({ borderRadius={'md'} > - {'avatar'} {model.name} - - {model.share.intro || '这个AI助手还没有介绍~'} - + + + {model.share.intro || '这个 应用 还没有介绍~'} + + + router.push(`/chat?modelId=${model._id}`)} > 体验 - {model.share.isShareDetail && ( - - )} diff --git a/src/pages/model/share/index.tsx b/src/pages/model/share/index.tsx index b0f815611..d2d861d63 100644 --- a/src/pages/model/share/index.tsx +++ b/src/pages/model/share/index.tsx @@ -1,12 +1,11 @@ -import React, { useState, useRef, useCallback, useMemo } from 'react'; +import React, { useState, useRef, useCallback } from 'react'; import { Box, Flex, Card, Grid, Input } from '@chakra-ui/react'; import { useLoading } from '@/hooks/useLoading'; -import { getShareModelList, triggerModelCollection, getCollectionModels } from '@/api/model'; +import { getShareModelList, triggerModelCollection } from '@/api/model'; import { usePagination } from '@/hooks/usePagination'; import type { ShareModelItem } from '@/types/model'; import { useUserStore } from '@/store/user'; import ShareModelList from './components/list'; -import { useQuery } from '@tanstack/react-query'; const modelList = () => { const { Loading } = useLoading(); @@ -15,7 +14,13 @@ const modelList = () => { const { refreshModel } = useUserStore(); /* 加载模型 */ - const { data, isLoading, Pagination, getData, pageNum } = usePagination({ + const { + data: models, + isLoading, + Pagination, + getData, + pageNum + } = usePagination({ api: getShareModelList, pageSize: 24, params: { @@ -23,65 +28,32 @@ const modelList = () => { } }); - const { data: collectionModels = [], refetch: refetchCollection } = useQuery( - ['getCollectionModels'], - getCollectionModels - ); - - const models = useMemo(() => { - if (!collectionModels) return []; - return data.map((model) => ({ - ...model, - isCollection: !!collectionModels.find((item) => item._id === model._id) - })); - }, [collectionModels, data]); - const onclickCollection = useCallback( async (modelId: string) => { try { await triggerModelCollection(modelId); getData(pageNum); - refetchCollection(); refreshModel.removeModelDetail(modelId); } catch (error) { console.log(error); } }, - [getData, pageNum, refetchCollection, refreshModel] + [getData, pageNum, refreshModel] ); return ( - - - 我收藏的AI助手 - - - {collectionModels.length == 0 && ( - - 还没有收藏AI助手~ - - )} - - - - - - - AI助手市场 - - (Beta) - + 应用市场 setSearchText(e.target.value)} onBlur={() => { if (searchText === lastSearch.current) return; @@ -98,7 +70,17 @@ const modelList = () => { /> - + diff --git a/src/pages/number/index.tsx b/src/pages/number/index.tsx index 6b5f0888c..19ba98bef 100644 --- a/src/pages/number/index.tsx +++ b/src/pages/number/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useState } from 'react'; -import { Card, Box, Flex, Button, Input, Image } from '@chakra-ui/react'; +import { Card, Box, Flex, Button, Input } from '@chakra-ui/react'; import { useForm } from 'react-hook-form'; import { UserUpdateParams } from '@/types/user'; import { putUserInfo } from '@/api/user'; @@ -14,6 +14,7 @@ import dynamic from 'next/dynamic'; import { useSelectFile } from '@/hooks/useSelectFile'; import { compressImg } from '@/utils/file'; import Loading from '@/components/Loading'; +import Avatar from '@/components/Avatar'; const PayRecordTable = dynamic(() => import('./components/PayRecordTable'), { loading: () => , @@ -106,12 +107,10 @@ const NumberSetting = () => { 头像: - {'avatar'} => { + return new Promise((resolve, reject) => { + // 获取 cookie + const cookies = Cookie.parse(cookie || ''); + const token = cookies.token; + + if (!token) { + return reject(ERROR_ENUM.unAuthorization); + } + + const key = process.env.TOKEN_KEY as string; + + jwt.verify(token, key, function (err, decoded: any) { + if (err || !decoded?.userId) { + reject(ERROR_ENUM.unAuthorization); + return; + } + resolve(decoded.userId); + }); + }); +}; + /* uniform auth user */ export const authUser = async ({ req, @@ -23,27 +45,6 @@ export const authUser = async ({ authOpenApi?: boolean; authRoot?: boolean; }) => { - const parseCookie = (cookie?: string): Promise => { - return new Promise((resolve, reject) => { - // 获取 cookie - const cookies = Cookie.parse(cookie || ''); - const token = cookies.token; - - if (!token) { - return reject(ERROR_ENUM.unAuthorization); - } - - const key = process.env.TOKEN_KEY as string; - - jwt.verify(token, key, function (err, decoded: any) { - if (err || !decoded?.userId) { - reject(ERROR_ENUM.unAuthorization); - return; - } - resolve(decoded.userId); - }); - }); - }; const parseOpenApiKey = async (apiKey?: string) => { if (!apiKey) { return Promise.reject(ERROR_ENUM.unAuthorization);