perf: text and avatar

This commit is contained in:
archer
2023-05-22 16:47:41 +08:00
parent 1c8db69a5a
commit ee2c259c3d
33 changed files with 231 additions and 277 deletions

View File

@@ -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"
}
}

View File

@@ -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<ShareModelItem[]>(`/model/share/getCollection`);
/**
* 收藏/取消收藏模型
*/

View File

@@ -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);
}

View File

@@ -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 (
<Image
fallbackSrc="/icon/logo.png"
borderRadius={'50%'}
objectFit={'contain'}
alt=""
w={w}
h={w}
{...props}
/>
);
};
export default Avatar;

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684739031957" class="icon" viewBox="0 0 1026 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4988" xmlns:xlink="http://www.w3.org/1999/xlink" width="64.125" height="64"><path d="M371.732817 94.172314q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 247.821878q0 25.773475-18.338819 44.112294t-44.112294 18.338819l-247.821878 0q-25.773475 0-43.616651-18.338819t-17.843175-44.112294l0-247.821878q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM371.732817 589.81607q25.773475 0 44.112294 17.843175t18.338819 43.616651l0 248.813166q0 25.773475-18.338819 43.616651t-44.112294 17.843175l-247.821878 0q-25.773475 0-43.616651-17.843175t-17.843175-43.616651l0-248.813166q0-25.773475 17.843175-43.616651t43.616651-17.843175l247.821878 0zM868.367861 589.81607q25.773475 0 43.616651 17.843175t17.843175 43.616651l0 248.813166q0 25.773475-17.843175 43.616651t-43.616651 17.843175l-247.821878 0q-25.773475 0-44.112294-17.843175t-18.338819-43.616651l0-248.813166q0-25.773475 18.338819-43.616651t44.112294-17.843175l247.821878 0zM1006.156825 203.21394q19.82575 19.82575 19.82575 46.590513t-19.82575 45.599226l-184.379477 184.379477q-19.82575 19.82575-46.094869 19.82575t-46.094869-19.82575l-184.379477-184.379477q-18.834463-18.834463-18.834463-45.599226t18.834463-46.590513l184.379477-184.379477q19.82575-18.834463 46.094869-18.834463t46.094869 18.834463z" p-id="4989"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684745011703" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1481" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M110.025 252.249c0 13.741 5.456 27.374 14.899 37.112s22.663 15.364 35.987 15.364 26.544-5.626 35.987-15.364c9.441-9.738 14.899-23.371 14.899-37.112s-5.456-27.375-14.899-37.111c-9.442-9.738-22.663-15.364-35.987-15.364s-26.544 5.626-35.987 15.364c-9.338 9.736-14.899 23.37-14.899 37.111m0 0zM103.625 512.575c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.371 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.738-22.662-15.364-36.091-15.364-13.324 0-26.649 5.626-36.091 15.364-9.444 9.737-14.899 23.37-14.899 37.22m0 0zM103.625 774.089c0 13.741 5.455 27.482 14.899 37.22 9.442 9.738 22.663 15.364 36.091 15.364 13.324 0 26.649-5.626 36.091-15.364s14.899-23.37 14.899-37.22c0-13.741-5.455-27.482-14.899-37.22-9.442-9.737-22.662-15.364-36.091-15.364-13.324 0-26.649 5.627-36.091 15.364-9.444 9.847-14.899 23.479-14.899 37.22m0 0zM919.041 249.869c0 27.699-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.397-44.59-50.095 0-27.699 19.935-50.095 44.59-50.095h528.571c24.656-0.001 44.59 22.396 44.59 50.095m0 0zM919.041 510.195c0 27.59-19.935 50.095-44.59 50.095H345.88c-24.655 0-44.59-22.398-44.59-50.096 0-27.699 19.935-50.096 44.59-50.096h528.571c24.656-0.109 44.59 22.397 44.59 50.097m0 0zM919.041 771.601c0 27.699-19.935 50.096-44.59 50.096H345.88c-24.655 0-44.59-22.397-44.59-50.096 0-27.591 19.935-49.988 44.59-49.988h528.571c24.656-0.108 44.59 22.397 44.59 49.988m0 0z" p-id="1482"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683450447995" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2005" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M728.99015111 121.90378667h107.92732444V40.96h-107.92732444v80.94378667z m175.38161778 67.45429333v107.92732445H985.31555555v-107.92732445h-80.94378666z m-67.45429334 175.38161778h-107.92732444v80.94378667h107.92732444v-80.94378667z m-175.38161777-67.45429333v-107.92732445h-80.94378667v107.92732445h80.94378667z m67.45429333 67.45429333c-37.23491555 0-67.45429333-30.21937778-67.45429333-67.45429333h-80.94378667c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378667z m175.38161778-67.45429333c0 37.23491555-30.21937778 67.45429333-67.45429334 67.45429333v80.94378667c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378666zM836.91747555 121.90151111c37.23491555 0 67.45429333 30.21937778 67.45429334 67.45429334H985.31555555C985.31555555 107.38801778 918.88753778 40.96 836.91747555 40.96v80.94378667zM728.99015111 40.96c-81.97006222 0-148.39808 66.42801778-148.39808 148.39808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333V40.96zM189.35808 661.53585778h107.92732445v-80.94378667h-107.92732445v80.94378667z m175.38161778 67.45429333v107.92732444h80.94378667v-107.92732444h-80.94378667z m-67.45429333 175.38161778h-107.92732445V985.31555555h107.92732445v-80.94378666zM121.90151111 836.91747555v-107.92732444H40.96v107.92732444h80.94378667z m67.45429334 67.45429334c-37.23491555 0-67.45429333-30.21937778-67.45429334-67.45429334H40.96C40.96 918.88753778 107.38801778 985.31555555 189.35808 985.31555555v-80.94378666z m175.38161777-67.45429334c0 37.23491555-30.21937778 67.45429333-67.45429333 67.45429334V985.31555555c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378667z m-67.45429333-175.38161777c37.23491555 0 67.45429333 30.21937778 67.45429333 67.45429333h80.94378667c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378667z m-107.92732444-80.94378667C107.38801778 580.59207111 40.96 647.02008889 40.96 728.99015111h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333v-80.94378667z m0-458.68828444h107.92732444V40.96h-107.92732444v80.94378667z m175.38161777 67.45429333v107.92732445h80.94378667v-107.92732445h-80.94378667z m-67.45429333 175.38161778h-107.92732444v80.94378667h107.92732444v-80.94378667zM121.90151111 297.28540445v-107.92732445H40.96v107.92732445h80.94378667z m67.45429334 67.45429333c-37.23491555 0-67.45429333-30.21937778-67.45429334-67.45429333H40.96c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378667z m175.38161777-67.45429333c0 37.23491555-30.21937778 67.45429333-67.45429333 67.45429333v80.94378667c81.97006222 0 148.39808-66.42801778 148.39808-148.39808h-80.94378667zM297.28540445 121.90151111c37.23491555 0 67.45429333 30.21937778 67.45429333 67.45429334h80.94378667c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378666zM189.35808 40.96C107.38801778 40.96 40.96 107.38801778 40.96 189.35808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333V40.96z m539.63207111 620.57585778h107.92732444v-80.94378667h-107.92732444v80.94378667z m175.38161778 67.45429333v107.92732444H985.31555555v-107.92732444h-80.94378666z m-67.45429334 175.38161778h-107.92732444V985.31555555h107.92732444v-80.94378666z m-175.38161777-67.45429334v-107.92732444h-80.94378667v107.92732444h80.94378667z m67.45429333 67.45429334c-37.23491555 0-67.45429333-30.21937778-67.45429333-67.45429334h-80.94378667c0 81.97006222 66.42801778 148.39808 148.39808 148.39808v-80.94378666z m175.38161778-67.45429334c0 37.23491555-30.21937778 67.45429333-67.45429334 67.45429334V985.31555555C918.88753778 985.31555555 985.31555555 918.88753778 985.31555555 836.91747555h-80.94378666z m-67.45429334-175.38161777c37.23491555 0 67.45429333 30.21937778 67.45429334 67.45429333H985.31555555c0-81.97006222-66.42801778-148.39808-148.39808-148.39808v80.94378667z m-107.92732444-80.94378667c-81.97006222 0-148.39808 66.42801778-148.39808 148.39808h80.94378667c0-37.23491555 30.21937778-67.45429333 67.45429333-67.45429333v-80.94378667z" p-id="2006"></path></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1684739068105" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7879" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M817.87 556.31h-63.58v-66.24A42.27 42.27 0 0 0 712 447.8h-84.81a42.27 42.27 0 0 0-42.27 42.27v66.24H436.57v-66.24a42.27 42.27 0 0 0-42.27-42.27h-84.83a42.27 42.27 0 0 0-42.27 42.27v66.24h-61.83A22.39 22.39 0 0 0 183 578.7a22.39 22.39 0 0 0 22.39 22.39h61.81v65.55a42.27 42.27 0 0 0 42.27 42.27h84.83a42.27 42.27 0 0 0 42.27-42.27v-65.55h148.36v65.55a42.27 42.27 0 0 0 42.27 42.27H712a42.27 42.27 0 0 0 42.27-42.27v-65.55h63.58a22.39 22.39 0 0 0 22.39-22.39 22.39 22.39 0 0 0-22.37-22.39z m-438.64 95.26h-54.69V505.14h54.69z m317.72 0h-54.69V505.14H697z" p-id="7880"></path><path d="M823 202.58h-90.81v-63.09a71.88 71.88 0 0 0-71.88-71.88H363.19a71.88 71.88 0 0 0-71.88 71.88v63.08h-90.12A137.17 137.17 0 0 0 64 339.75v479a137.17 137.17 0 0 0 137.19 137.14H823a137.17 137.17 0 0 0 137.19-137.17v-479A137.17 137.17 0 0 0 823 202.58z m-474.36-54.1A23.52 23.52 0 0 1 372.17 125h279.16a23.52 23.52 0 0 1 23.52 23.52v54.1h-326.2z m554.23 673.31a76.76 76.76 0 0 1-76.76 76.76h-628a76.76 76.76 0 0 1-76.76-76.76V336.67a76.76 76.76 0 0 1 76.76-76.76h628a76.76 76.76 0 0 1 76.76 76.76z" p-id="7881"></path></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683254591061" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1213" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M389.5296 650.78613333a204.8 204.8 0 1 1-10.82026667-288.49493333L557.43146667 245.76a153.6 153.6 0 1 1 39.25333333 55.9104l-176.46933333 115.02933333c15.01866667 28.50133333 23.48373333 60.928 23.48373333 95.3344a204.11733333 204.11733333 0 0 1-16.86186667 81.47626667l257.1264 144.62293333a153.6 153.6 0 1 1-30.9248 60.928l-263.54346666-148.24106666z" p-id="1214"></path></svg>

Before

Width:  |  Height:  |  Size: 710 B

View File

@@ -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;

View File

@@ -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')}
>
<Image
src={userInfo?.avatar || '/icon/human.png'}
objectFit={'contain'}
w={'36px'}
h={'36px'}
alt=""
/>
<Avatar w={'36px'} h={'36px'} src={userInfo?.avatar} fallbackSrc={'/icon/human.png'} />
</Box>
{/* 导航列表 */}
<Box flex={1}>

View File

@@ -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']

View File

@@ -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;
}

View File

@@ -27,6 +27,6 @@ export enum PromotionEnum {
export const PromotionTypeMap = {
[PromotionEnum.invite]: '好友充值',
[PromotionEnum.shareModel]: 'AI助手分享',
[PromotionEnum.shareModel]: '应用分享',
[PromotionEnum.withdraw]: '提现'
};

View File

@@ -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 = <T = any,>({
api,

View File

@@ -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
});

View File

@@ -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<any>) {
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<ShareModelItem[]>(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: []
});
}
}

View File

@@ -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<any>) {
@@ -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
}
});

View File

@@ -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 && (
<Card p={4} mb={10}>
<Flex mb={2} alignItems={'center'} justifyContent={'center'}>
<Image
src={avatar || LOGO_ICON}
w={'32px'}
maxH={'40px'}
objectFit={'contain'}
alt={''}
/>
<Avatar src={avatar} w={'32px'} h={'32px'} />
<Box ml={3} fontSize={'3xl'} fontWeight={'bold'}>
{name}
</Box>

View File

@@ -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}`);
}}
>
<Image
src={item.avatar || '/icon/logo.png'}
alt=""
w={'34px'}
maxH={'50px'}
objectFit={'contain'}
/>
<Avatar src={item.avatar} w={'34px'} h={'34px'} />
<Box flex={'1 0 0'} w={0} ml={3}>
<Box className="textEllipsis" color={'myGray.1000'}>
{item.name}
</Box>
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
{item.systemPrompt || '这个AI助手没有设置提示词~'}
{item.systemPrompt || '这个 应用 没有设置提示词~'}
</Box>
</Box>
</Flex>

View File

@@ -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'}
>
<Flex alignItems={'center'} justifyContent={'space-between'} px={3}>
<Box flex={'0 0 50px'}>AI助手</Box>
<Box flex={'0 0 50px'}>AI应用</Box>
{/* 新对话 */}
<Button
w={'50%'}
@@ -115,7 +115,7 @@ const PhoneSliderBar = ({
onClose();
}}
>
<Image src={item.avatar || '/icon/logo.png'} mr={2} alt={''} w={'16px'} h={'16px'} />
<Avatar src={item.avatar} mr={2} w={'18px'} h={'18px'} />
<Box className={'textEllipsis'} flex={'1 0 0'} w={0}>
{item.name}
</Box>

View File

@@ -16,7 +16,6 @@ import {
MenuButton,
MenuList,
MenuItem,
Image,
Button,
Modal,
ModalOverlay,
@@ -51,6 +50,7 @@ import { useUserStore } from '@/store/user';
import Loading from '@/components/Loading';
import Markdown from '@/components/Markdown';
import SideBar from '@/components/SideBar';
import Avatar from '@/components/Avatar';
import Empty from './components/Empty';
const PhoneSliderBar = dynamic(() => import('./components/PhoneSliderBar'), {
@@ -595,7 +595,7 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
borderBottom={theme.borders.base}
onClick={() => router.push(`/model?modelId=${chatData.modelId}`)}
>
AI助手详
</MenuItem>
)}
{hasVoiceApi && (
@@ -654,9 +654,9 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
>
{!isPc && (
<MyIcon
name={'tabbarMore'}
w={'14px'}
h={'14px'}
name={'menu'}
w={'20px'}
h={'20px'}
color={useColorModeValue('blackAlpha.700', 'white')}
onClick={onOpenSlider}
/>
@@ -734,19 +734,15 @@ const Chat = ({ modelId, chatId }: { modelId: string; chatId: string }) => {
ml: ['6px', 2]
})}
>
<Tooltip label={item.obj === 'AI' ? 'AI助手详情' : ''}>
<Image
className="avatar"
<Tooltip label={item.obj === 'AI' ? '应用详情' : ''}>
<Avatar
src={
item.obj === 'Human'
? userInfo?.avatar || '/icon/human.png'
: chatData.model.avatar || LOGO_ICON
}
alt="avatar"
w={['20px', '34px']}
h={['20px', '34px']}
borderRadius={'50%'}
objectFit={'contain'}
/>
</Tooltip>
</MenuButton>

View File

@@ -11,7 +11,6 @@ import {
MenuButton,
MenuList,
MenuItem,
Image,
Button,
Modal,
ModalOverlay,
@@ -48,6 +47,7 @@ import { useUserStore } from '@/store/user';
import Loading from '@/components/Loading';
import Markdown from '@/components/Markdown';
import SideBar from '@/components/SideBar';
import Avatar from '@/components/Avatar';
import Empty from './components/Empty';
const ShareHistory = dynamic(() => import('./components/ShareHistory'), {
@@ -564,9 +564,9 @@ const Chat = ({ shareId, historyId }: { shareId: string; historyId: string }) =>
>
{!isPc && (
<MyIcon
name={'tabbarMore'}
w={'14px'}
h={'14px'}
name={'menu'}
w={'20px'}
h={'20px'}
color={useColorModeValue('blackAlpha.700', 'white')}
onClick={onOpenSlider}
/>
@@ -626,19 +626,15 @@ const Chat = ({ shareId, historyId }: { shareId: string; historyId: string }) =>
ml: ['6px', 2]
})}
>
<Tooltip label={item.obj === 'AI' ? 'AI助手详情' : ''}>
<Image
className="avatar"
<Tooltip label={item.obj === 'AI' ? '应用详情' : ''}>
<Avatar
src={
item.obj === 'Human'
? userInfo?.avatar || '/icon/human.png'
: shareChatData.model.avatar || LOGO_ICON
}
alt="avatar"
w={['20px', '34px']}
h={['20px', '34px']}
borderRadius={'50%'}
objectFit={'contain'}
/>
</Tooltip>
</MenuButton>

View File

@@ -6,7 +6,6 @@ import {
Flex,
Button,
Tooltip,
Image,
FormControl,
Input,
Tag,
@@ -18,13 +17,13 @@ import { useForm } from 'react-hook-form';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { delKbById, putKbById } from '@/api/plugins/kb';
import { useLoading } from '@/hooks/useLoading';
import { KbItemType } from '@/types/plugin';
import { useSelectFile } from '@/hooks/useSelectFile';
import { useConfirm } from '@/hooks/useConfirm';
import { compressImg } from '@/utils/file';
import DataCard from './DataCard';
import { getErrText } from '@/utils/tools';
import Avatar from '@/components/Avatar';
const Detail = ({ kbId }: { kbId: string }) => {
const { toast } = useToast();
@@ -58,7 +57,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
},
onError(err: any) {
toast({
title: getErrText(err, '获取AI助手异常'),
title: getErrText(err, '获取知识库异常'),
status: 'error'
});
loadKbList(true);
@@ -95,6 +94,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
id: kbId,
...data
});
await getKbDetail(kbId, true);
toast({
title: '更新成功',
status: 'success'
@@ -108,7 +108,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
}
setBtnLoading(false);
},
[kbId, loadKbList, toast]
[getKbDetail, kbId, loadKbList, toast]
);
const saveSubmitError = useCallback(() => {
// deep search message
@@ -138,7 +138,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
maxH: 100
});
setValue('avatar', base64);
loadKbList(true);
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
@@ -146,7 +146,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
});
}
},
[loadKbList, setValue, toast]
[setRefresh, setValue, toast]
);
return (
@@ -180,12 +180,10 @@ const Detail = ({ kbId }: { kbId: string }) => {
<Box flex={'0 0 60px'} w={0}>
</Box>
<Image
src={getValues('avatar') || '/icon/logo.png'}
alt={'avatar'}
<Avatar
src={getValues('avatar')}
w={['28px', '36px']}
h={['28px', '36px']}
objectFit={'cover'}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useState, useMemo } from 'react';
import { Box, Flex, useTheme, Input, IconButton, Tooltip, Image, Tag } from '@chakra-ui/react';
import { Box, Flex, useTheme, Input, IconButton, Tooltip, Tag } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import { postCreateKb } from '@/api/plugins/kb';
@@ -8,6 +8,7 @@ import { useToast } from '@/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
const KbList = ({ kbId }: { kbId: string }) => {
const theme = useTheme();
@@ -111,13 +112,7 @@ const KbList = ({ kbId }: { kbId: string }) => {
router.push(`/kb?kbId=${item._id}`);
}}
>
<Image
src={item.avatar || '/icon/logo.png'}
alt=""
w={'34px'}
maxH={'50px'}
objectFit={'contain'}
/>
<Avatar src={item.avatar} w={'34px'} h={'34px'} />
<Box flex={'1 0 0'} w={0} ml={3}>
<Box className="textEllipsis" color={'myGray.1000'}>
{item.name}

View File

@@ -65,7 +65,7 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => {
});
// aut register a model
postCreateModel({
name: 'AI助手1'
name: '应用1'
});
} catch (error: any) {
toast({

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, useTheme, Input, IconButton, Tooltip, Image } from '@chakra-ui/react';
import { Box, Flex, useTheme, Input, IconButton, Tooltip } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import MyIcon from '@/components/Icon';
@@ -8,6 +8,7 @@ import { useLoading } from '@/hooks/useLoading';
import { useToast } from '@/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import Avatar from '@/components/Avatar';
const ModelList = ({ modelId }: { modelId: string }) => {
const theme = useTheme();
@@ -23,7 +24,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
const onclickCreateModel = useCallback(async () => {
setIsLoading(true);
try {
const id = await postCreateModel({ name: `AI助手${myModels.length + 1}` });
const id = await postCreateModel({ name: `AI应用${myModels.length + 1}` });
toast({
title: '创建成功',
status: 'success'
@@ -94,7 +95,7 @@ const ModelList = ({ modelId }: { modelId: string }) => {
/>
)}
</Flex>
<Tooltip label={'新建一个AI助手'}>
<Tooltip label={'新建一个AI应用'}>
<IconButton
h={'32px'}
icon={<AddIcon />}
@@ -134,19 +135,13 @@ const ModelList = ({ modelId }: { modelId: string }) => {
router.push(`/model?modelId=${item._id}`);
}}
>
<Image
src={item.avatar || '/icon/logo.png'}
alt=""
w={'34px'}
maxH={'50px'}
objectFit={'contain'}
/>
<Avatar src={item.avatar} w={'34px'} h={'34px'} />
<Box flex={'1 0 0'} w={0} ml={3}>
<Box className="textEllipsis" color={'myGray.1000'}>
{item.name}
</Box>
<Box className="textEllipsis" color={'myGray.400'} fontSize={'sm'}>
{item.systemPrompt || '这个AI助手没有设置提示词~'}
{item.systemPrompt || '这个 应用 没有设置提示词~'}
</Box>
</Box>
</Flex>

View File

@@ -15,7 +15,6 @@ import {
Button,
Select,
Switch,
Image,
Modal,
ModalOverlay,
ModalContent,
@@ -51,6 +50,7 @@ import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import Avatar from '@/components/Avatar';
const ModelEditForm = ({
formHooks,
@@ -71,7 +71,7 @@ const ModelEditForm = ({
const { loadKbList } = useUserStore();
const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该AI助手?'
content: '确认删除该应用?'
});
const { copyData } = useCopyData();
const { register, setValue, getValues } = formHooks;
@@ -189,13 +189,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
onClick={() => router.push(`/kb?kbId=${item._id}`)}
>
<Flex alignItems={'center'}>
<Image
src={item.avatar}
fallbackSrc="/icon/logo.png"
w={'20px'}
h={'20px'}
alt=""
></Image>
<Avatar src={item.avatar} w={'20px'} h={'20px'}></Avatar>
<Box ml={3} fontWeight={'bold'}>
{item.name}
</Box>
@@ -222,12 +216,10 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Box flex={'0 0 80px'} w={0}>
</Box>
<Image
src={getValues('avatar') || '/icon/logo.png'}
alt={'avatar'}
<Avatar
src={getValues('avatar')}
w={['28px', '36px']}
h={['28px', '36px']}
objectFit={'cover'}
cursor={isOwner ? 'pointer' : 'default'}
title={'点击切换头像'}
onClick={() => isOwner && onOpenSelectFile()}
@@ -283,14 +275,14 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
{isOwner && (
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 100px'}>AI助手</Box>
<Box flex={'0 0 100px'}></Box>
<Button
colorScheme={'gray'}
variant={'outline'}
size={'sm'}
onClick={openConfirm(handleDelModel)}
>
AI助手
</Button>
</Flex>
)}
@@ -621,13 +613,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
}}
>
<Flex alignItems={'center'}>
<Image
src={item.avatar}
fallbackSrc="/icon/logo.png"
w={'20px'}
h={'20px'}
alt=""
></Image>
<Avatar src={item.avatar} w={'20px'} h={'20px'} />
<Box ml={3} fontWeight={'bold'}>
{item.name}
</Box>

View File

@@ -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('');

View File

@@ -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'}
>
<Flex alignItems={'center'}>
<Image
<Avatar
src={model.avatar}
alt={'avatar'}
w={['28px', '36px']}
h={['28px', '36px']}
objectFit={'cover'}
borderRadius={'50%'}
/>
<Box fontWeight={'bold'} fontSize={'lg'} ml={5}>
{model.name}
</Box>
</Flex>
<Box
flex={1}
className={styles.intro}
my={4}
fontSize={'sm'}
wordBreak={'break-all'}
color={'blackAlpha.600'}
>
{model.share.intro || '这个AI助手还没有介绍~'}
</Box>
<Tooltip label={model.share.intro}>
<Box
className={styles.intro}
flex={1}
my={4}
fontSize={'sm'}
wordBreak={'break-all'}
color={'blackAlpha.600'}
>
{model.share.intro || '这个 应用 还没有介绍~'}
</Box>
</Tooltip>
<Flex justifyContent={'space-between'}>
<Flex
alignItems={'center'}
@@ -66,21 +69,11 @@ const ShareModelList = ({
<Button
size={'sm'}
variant={'outline'}
w={['60px', '80px']}
w={['60px', '70px']}
onClick={() => router.push(`/chat?modelId=${model._id}`)}
>
</Button>
{model.share.isShareDetail && (
<Button
ml={4}
size={'sm'}
w={['60px', '80px']}
onClick={() => router.push(`/model?modelId=${model._id}`)}
>
</Button>
)}
</Box>
</Flex>
</Flex>

View File

@@ -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<ShareModelItem>({
const {
data: models,
isLoading,
Pagination,
getData,
pageNum
} = usePagination<ShareModelItem>({
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 (
<Box py={[5, 10]} px={'5vw'}>
<Card px={6} py={3}>
<Flex alignItems={'center'} justifyContent={'space-between'}>
<Box fontWeight={'bold'} fontSize={'xl'}>
AI助手
</Box>
</Flex>
{collectionModels.length == 0 && (
<Box textAlign={'center'} pt={3}>
AI助手~
</Box>
)}
<Grid templateColumns={['1fr', '1fr 1fr', '1fr 1fr 1fr']} gridGap={4} mt={4}>
<ShareModelList models={collectionModels} onclickCollection={onclickCollection} />
</Grid>
</Card>
<Card mt={5} px={6} py={3}>
<Box display={['block', 'flex']} alignItems={'center'} justifyContent={'space-between'}>
<Box fontWeight={'bold'} flex={1} fontSize={'xl'}>
AI助手市
<Box as={'span'} fontWeight={'normal'} fontSize={'md'}>
(Beta)
</Box>
</Box>
<Box mt={[2, 0]} textAlign={'right'}>
<Input
maxW={'240px'}
w={['200px', '250px']}
size={'sm'}
value={searchText}
placeholder="搜索AI助手,回车确认"
placeholder="搜索应用,回车确认"
onChange={(e) => setSearchText(e.target.value)}
onBlur={() => {
if (searchText === lastSearch.current) return;
@@ -98,7 +70,17 @@ const modelList = () => {
/>
</Box>
</Box>
<Grid templateColumns={['1fr', '1fr 1fr', '1fr 1fr 1fr']} gridGap={4} mt={4}>
<Grid
templateColumns={[
'repeat(1,1fr)',
'repeat(2,1fr)',
'repeat(3,1fr)',
'repeat(4,1fr)',
'repeat(5,1fr)'
]}
gridGap={4}
mt={4}
>
<ShareModelList models={models} onclickCollection={onclickCollection} />
</Grid>
<Flex mt={4} justifyContent={'flex-end'}>

View File

@@ -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: () => <Loading fixed={false} />,
@@ -106,12 +107,10 @@ const NumberSetting = () => {
</Flex>
<Flex mt={6} alignItems={'center'}>
<Box flex={'0 0 50px'}>:</Box>
<Image
<Avatar
src={userInfo?.avatar}
alt={'avatar'}
w={['28px', '36px']}
maxH={'40px'}
objectFit={'contain'}
h={['28px', '36px']}
cursor={'pointer'}
title={'点击切换头像'}
onClick={onOpenSelectFile}

View File

@@ -11,8 +11,8 @@ const list = [
link: '/kb'
},
{
icon: 'shareMarket',
label: 'AI助手市场',
icon: 'appStore',
label: 'AI应用市场',
link: '/model/share'
},
{

View File

@@ -11,6 +11,28 @@ import { ERROR_ENUM } from '../errorCode';
import { ChatModelType, OpenAiChatEnum } from '@/constants/model';
import { hashPassword } from '@/service/utils/tools';
export const parseCookie = (cookie?: string): Promise<string> => {
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<string> => {
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);