This commit is contained in:
archer
2023-07-04 15:39:57 +08:00
parent 9bdd5f522d
commit 6e1ef89d65
44 changed files with 213 additions and 1216 deletions

View File

@@ -1,4 +1,4 @@
import { Model, Kb } from '../schema.js';
import { App, Kb } from '../schema.js';
import { auth } from './system.js';
export const useAppRoute = (app) => {
@@ -17,7 +17,7 @@ export const useAppRoute = (app) => {
...(id && { _id: id })
};
const modelsRaw = await Model.find(where)
const modelsRaw = await App.find(where)
.skip(start)
.limit(end - start)
.sort({ [sort]: order, 'share.isShare': -1, 'share.collection': -1 });
@@ -50,7 +50,7 @@ export const useAppRoute = (app) => {
models.push(orderedModel);
}
const totalCount = await Model.countDocuments(where);
const totalCount = await App.countDocuments(where);
res.header('Access-Control-Expose-Headers', 'X-Total-Count');
res.header('X-Total-Count', totalCount);
res.json(models);
@@ -70,7 +70,7 @@ export const useAppRoute = (app) => {
intro
} = req.body;
await Model.findByIdAndUpdate(_id, {
await App.findByIdAndUpdate(_id, {
$set: {
intro: intro,
'share.topNum': Number(topNum),

View File

@@ -56,7 +56,7 @@ const kbSchema = new mongoose.Schema({
__v: Number
});
const modelSchema = new mongoose.Schema({
const appSchema = new mongoose.Schema({
userId: mongoose.Schema.Types.ObjectId,
name: String,
avatar: String,
@@ -104,7 +104,7 @@ const SystemSchema = new mongoose.Schema({
}
});
export const Model = mongoose.models['model'] || mongoose.model('model', modelSchema);
export const App = mongoose.models['model'] || mongoose.model('model', appSchema);
export const Kb = mongoose.models['kb'] || mongoose.model('kb', kbSchema);
export const User = mongoose.models['user'] || mongoose.model('user', userSchema);
export const Pay = mongoose.models['pay'] || mongoose.model('pay', paySchema);

View File

@@ -1,44 +1,44 @@
import { GET, POST, DELETE, PUT } from './request';
import type { AppSchema } from '@/types/mongoSchema';
import type { ModelUpdateParams } from '@/types/model';
import type { AppUpdateParams } from '@/types/app';
import { RequestPaging } from '../types/index';
import type { ModelListResponse } from './response/model';
import type { AppListResponse } from './response/app';
/**
* 获取模型列表
*/
export const getMyModels = () => GET<ModelListResponse>('/model/list');
export const getMyModels = () => GET<AppListResponse>('/app/list');
/**
* 创建一个模型
*/
export const postCreateModel = (data: { name: string }) => POST<string>('/model/create', data);
export const postCreateModel = (data: { name: string }) => POST<string>('/app/create', data);
/**
* 根据 ID 删除模型
*/
export const delModelById = (id: string) => DELETE(`/model/del?modelId=${id}`);
export const delModelById = (id: string) => DELETE(`/app/del?modelId=${id}`);
/**
* 根据 ID 获取模型
*/
export const getModelById = (id: string) => GET<AppSchema>(`/model/detail?modelId=${id}`);
export const getModelById = (id: string) => GET<AppSchema>(`/app/detail?modelId=${id}`);
/**
* 根据 ID 更新模型
*/
export const putAppById = (id: string, data: ModelUpdateParams) =>
PUT(`/model/update?appId=${id}`, data);
export const putAppById = (id: string, data: AppUpdateParams) =>
PUT(`/app/update?appId=${id}`, data);
/* 共享市场 */
/**
* 获取共享市场模型
*/
export const getShareModelList = (data: { searchText?: string } & RequestPaging) =>
POST(`/model/share/getModels`, data);
POST(`/app/share/getModels`, data);
/**
* 收藏/取消收藏模型
*/
export const triggerModelCollection = (modelId: string) =>
POST<number>(`/model/share/collection?modelId=${modelId}`);
POST<number>(`/app/share/collection?modelId=${modelId}`);

View File

@@ -3,7 +3,7 @@ import type { HistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/model';
import type { ShareChatEditType } from '@/types/app';
import { Obj2Query } from '@/utils/tools';
import type { QuoteItemType } from '@/pages/api/openapi/kb/appKbSearch';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';

6
client/src/api/response/app.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
import { AppListItemType } from '@/types/app';
export type AppListResponse = {
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
};

View File

@@ -1,6 +0,0 @@
import { ModelListItemType } from '@/types/model';
export type ModelListResponse = {
myApps: ModelListItemType[];
myCollectionApps: ModelListItemType[];
};

View File

@@ -7,7 +7,7 @@ import { useQuery } from '@tanstack/react-query';
const unAuthPage: { [key: string]: boolean } = {
'/': true,
'/login': true,
'/model/share': true,
'/appStore': true,
'/chat/share': true
};

View File

@@ -32,12 +32,6 @@ const Navbar = ({ unread }: { unread: number }) => {
link: `/app/list`,
activeLink: ['/app/list']
},
{
label: '旧应用',
icon: 'model',
link: `/model?modelId=${lastModelId}`,
activeLink: ['/model']
},
{
label: '知识库',
icon: 'kb',
@@ -47,8 +41,8 @@ const Navbar = ({ unread }: { unread: number }) => {
{
label: '市场',
icon: 'appStore',
link: '/model/share',
activeLink: ['/model/share']
link: '/appStore',
activeLink: ['/appStore']
},
{
label: '账号',
@@ -57,7 +51,7 @@ const Navbar = ({ unread }: { unread: number }) => {
activeLink: ['/number']
}
],
[lastChatId, lastChatModelId, lastModelId]
[lastChatId, lastChatModelId]
);
const itemStyles: any = {

View File

@@ -1,4 +1,5 @@
import type { ShareChatEditType } from '@/types/model';
import { getSystemModelList } from '@/api/system';
import type { ShareChatEditType } from '@/types/app';
import type { AppSchema } from '@/types/mongoSchema';
export const embeddingModel = 'text-embedding-ada-002';

View File

@@ -274,7 +274,7 @@ export const theme = extendTheme({
borders: {
sm: '1px solid #EFF0F1',
base: '1px solid #DEE0E2',
md: '1px solid #BDC1C5'
md: '1px solid #DAE0E2'
},
breakpoints: {
sm: '900px',

View File

@@ -3,7 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Model } from '@/service/models/model';
import { App } from '@/service/models/model';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
@@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
// 上限校验
const authCount = await Model.countDocuments({
const authCount = await App.countDocuments({
userId
});
if (authCount >= 50) {
@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}
// 创建模型
const response = await Model.create({
const response = await App.create({
name,
userId
});

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, Model, connectToDatabase, Collection, ShareChat } from '@/service/mongo';
import { Chat, App, connectToDatabase, Collection, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
@@ -40,7 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// 删除模型
await Model.deleteOne({
await App.deleteOne({
_id: modelId,
userId
});

View File

@@ -1,8 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection, Model } from '@/service/mongo';
import { connectToDatabase, Collection, App } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import type { ModelListResponse } from '@/api/response/model';
import type { AppListResponse } from '@/api/response/app';
/* 获取模型列表 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 根据 userId 获取模型信息
const [myApps, myCollections] = await Promise.all([
Model.find(
App.find(
{
userId
},
@@ -31,7 +31,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
.then((res) => res.filter((item) => item.modelId))
]);
jsonRes<ModelListResponse>(res, {
jsonRes<AppListResponse>(res, {
data: {
myApps: myApps.map((item) => ({
_id: item._id,

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Collection, Model } from '@/service/mongo';
import { connectToDatabase, Collection, App } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
/* 模型收藏切换 */
@@ -30,7 +30,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
}
await Model.findByIdAndUpdate(modelId, {
await App.findByIdAndUpdate(modelId, {
'share.collection': await Collection.countDocuments({ modelId })
});

View File

@@ -1,8 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Model } from '@/service/mongo';
import { connectToDatabase, App } from '@/service/mongo';
import type { PagingData } from '@/types';
import type { ShareModelItem } from '@/types/model';
import type { ShareAppItem } from '@/types/app';
import { parseCookie } from '@/service/utils/auth';
import { Types } from 'mongoose';
@@ -91,11 +91,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// 获取被分享的模型
const [models, total] = await Promise.all([
// @ts-ignore
Model.aggregate(pipeline),
Model.countDocuments(where)
App.aggregate(pipeline),
App.countDocuments(where)
]);
jsonRes<PagingData<ShareModelItem>>(res, {
jsonRes<PagingData<ShareAppItem>>(res, {
data: {
pageNum,
pageSize,

View File

@@ -2,14 +2,14 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Model } from '@/service/models/model';
import type { ModelUpdateParams } from '@/types/model';
import { App } from '@/service/models/model';
import type { AppUpdateParams } from '@/types/app';
import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { name, avatar, chat, share, intro, modules } = req.body as ModelUpdateParams;
const { name, avatar, chat, share, intro, modules } = req.body as AppUpdateParams;
const { appId } = req.query as { appId: string };
if (!appId) {
@@ -27,7 +27,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// 更新模型
await Model.updateOne(
await App.updateOne(
{
_id: appId,
userId

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, Chat, Model } from '@/service/mongo';
import { connectToDatabase, Chat, App } from '@/service/mongo';
import type { InitChatResponse } from '@/api/response/chat';
import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
@@ -23,13 +23,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 没有 modelId 时直接获取用户的第一个id
const app = await (async () => {
if (!modelId) {
const myModel = await Model.findOne({ userId });
const myModel = await App.findOne({ userId });
if (!myModel) {
const { _id } = await Model.create({
const { _id } = await App.create({
name: '应用1',
userId
});
return (await Model.findById(_id)) as AppSchema;
return (await App.findById(_id)) as AppSchema;
} else {
return myModel;
}

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { ChatItemType } from '@/types/chat';
import { connectToDatabase, Chat, Model } from '@/service/mongo';
import { connectToDatabase, Chat, App } from '@/service/mongo';
import { authApp } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
@@ -60,7 +60,7 @@ export async function saveChat({
}));
if (String(app.userId) === userId) {
await Model.findByIdAndUpdate(modelId, {
await App.findByIdAndUpdate(modelId, {
updateTime: new Date()
});
}
@@ -96,7 +96,7 @@ export async function saveChat({
// update app
...(String(app.userId) === userId
? [
Model.findByIdAndUpdate(modelId, {
App.findByIdAndUpdate(modelId, {
updateTime: new Date()
})
]

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authApp, authUser } from '@/service/utils/auth';
import type { ShareChatEditType } from '@/types/model';
import type { ShareChatEditType } from '@/types/app';
/* create a shareChat */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {

View File

@@ -117,6 +117,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
},
stream
});
console.log(responseData, answerText);
// save chat
if (typeof chatId === 'string') {
@@ -282,14 +283,17 @@ async function dispatchModules({
if (res.closed) return Promise.resolve();
console.log('run=========', module.type, module.url);
// direct answer
if (module.type === AppModuleItemTypeEnum.answer) {
const text =
module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value || '';
pushStore({
answer: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value || ''
answer: text
});
return StreamAnswer({
res,
stream,
text: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value
text: text
});
}
@@ -365,9 +369,9 @@ function StreamAnswer({
}: {
res: NextApiResponse;
stream?: boolean;
text?: '';
text?: string;
}) {
if (stream) {
if (stream && text) {
return sseResponse({
res,
event: sseResponseEventEnum.answer,

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB, Model, TrainingData } from '@/service/mongo';
import { connectToDatabase, KB, App, TrainingData } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { Types } from 'mongoose';
@@ -32,7 +32,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
});
// delete related model
await Model.updateMany(
await App.updateMany(
{
userId
},

View File

@@ -36,7 +36,7 @@ import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat'
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
import { useForm } from 'react-hook-form';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/model';
import type { ShareChatEditType } from '@/types/app';
const Share = ({ modelId }: { modelId: string }) => {
const { toast } = useToast();

View File

@@ -4,8 +4,8 @@ import { Box, Flex } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
import { useGlobalStore } from '@/store/global';
import dynamic from 'next/dynamic';
import Tabs from '@/components/Tabs';
import Tabs from '@/components/Tabs';
import Settings from './components/Settings';
import { defaultApp } from '@/constants/model';

View File

@@ -1,24 +1,81 @@
import React from 'react';
import { Box, useTheme } from '@chakra-ui/react';
import { Box, Grid, Card, useTheme, Flex, IconButton, Button } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user';
import { useQuery } from '@tanstack/react-query';
import Avatar from '@/components/Avatar';
import styles from './index.module.scss';
import MyIcon from '@/components/Icon';
import { AddIcon } from '@chakra-ui/icons';
const MyApps = () => {
const theme = useTheme();
const router = useRouter();
const { myApps, loadMyModels } = useUserStore();
/* 加载模型 */
useQuery(['loadModels'], () => loadMyModels(false));
return (
<Box>
<Box
className="textlg"
borderBottom={theme.borders.base}
letterSpacing={1}
py={3}
px={5}
fontSize={'24px'}
fontWeight={'bold'}
onClick={() => router.push(`/app/detail?appId=642adec15f01d67d4613efdb`)}
<Flex py={3} px={5} borderBottom={theme.borders.base} alignItems={'center'}>
<Box flex={1} className="textlg" letterSpacing={1} fontSize={'24px'} fontWeight={'bold'}>
</Box>
<Button leftIcon={<AddIcon />} variant={'base'}>
</Button>
</Flex>
<Grid
p={5}
gridTemplateColumns={['1fr', 'repeat(3,1fr)', 'repeat(4,1fr)', 'repeat(5,1fr)']}
gridGap={5}
>
</Box>
{myApps.map((app) => (
<Card
key={app._id}
py={4}
px={5}
cursor={'pointer'}
h={'140px'}
border={theme.borders.md}
boxShadow={'none'}
userSelect={'none'}
_hover={{
boxShadow: 'xl',
transform: 'scale(1.03)',
borderColor: 'transparent',
'& .delete': {
display: 'block'
}
}}
onClick={() => router.push(`/app/detail?appId=${app._id}`)}
>
<Flex alignItems={'center'} h={'38px'} position={'relative'}>
<Avatar src={app.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3}>{app.name}</Box>
<IconButton
className="delete"
position={'absolute'}
right={0}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
display={'none'}
_hover={{
bg: 'myGray.100'
}}
/>
</Flex>
<Box className={styles.intro} py={2} fontSize={'sm'} color={'myGray.600'}>
{app.intro || '这个应用还没写介绍~'}
</Box>
</Card>
))}
</Grid>
</Box>
);
};

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Box, Flex, Button, Tooltip, Card } from '@chakra-ui/react';
import type { ShareModelItem } from '@/types/model';
import type { ShareAppItem } from '@/types/app';
import { useRouter } from 'next/router';
import MyIcon from '@/components/Icon';
import styles from '../index.module.scss';
@@ -10,7 +10,7 @@ const ShareModelList = ({
models = [],
onclickCollection
}: {
models: ShareModelItem[];
models: ShareAppItem[];
onclickCollection: (modelId: string) => void;
}) => {
const router = useRouter();

View File

@@ -0,0 +1,7 @@
.intro {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -3,7 +3,7 @@ import { Box, Flex, Card, Grid, Input } from '@chakra-ui/react';
import { useLoading } from '@/hooks/useLoading';
import { getShareModelList, triggerModelCollection } from '@/api/app';
import { usePagination } from '@/hooks/usePagination';
import type { ShareModelItem } from '@/types/model';
import type { ShareAppItem } from '@/types/app';
import { useUserStore } from '@/store/user';
import ShareModelList from './components/list';
import styles from './index.module.scss';
@@ -21,7 +21,7 @@ const modelList = () => {
Pagination,
getData,
pageNum
} = usePagination<ShareModelItem>({
} = usePagination<ShareAppItem>({
api: getShareModelList,
pageSize: 24,
params: {

View File

@@ -1,10 +1,10 @@
import React from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import { ModelListItemType } from '@/types/model';
import { AppListItemType } from '@/types/app';
import Avatar from '@/components/Avatar';
const ModelList = ({ models, modelId }: { models: ModelListItemType[]; modelId: string }) => {
const ModelList = ({ models, modelId }: { models: AppListItemType[]; modelId: string }) => {
const router = useRouter();
return (

View File

@@ -1,172 +0,0 @@
import React, { useCallback, useMemo, useState } from 'react';
import { Box, Flex, Input, IconButton, Tooltip, useTheme } from '@chakra-ui/react';
import { AddIcon } from '@chakra-ui/icons';
import { useRouter } from 'next/router';
import MyIcon from '@/components/Icon';
import { postCreateModel } from '@/api/app';
import { useLoading } from '@/hooks/useLoading';
import { useToast } from '@/hooks/useToast';
import { useQuery } from '@tanstack/react-query';
import { useUserStore } from '@/store/user';
import { MyModelsTypeEnum } from '@/constants/user';
import dynamic from 'next/dynamic';
const Avatar = dynamic(() => import('@/components/Avatar'), {
ssr: false
});
const Tabs = dynamic(() => import('@/components/Tabs'), {
ssr: false
});
const ModelList = ({ modelId }: { modelId: string }) => {
const [currentTab, setCurrentTab] = useState(MyModelsTypeEnum.my);
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { Loading, setIsLoading } = useLoading();
const { myApps, myCollectionApps, loadMyModels, refreshModel } = useUserStore();
const [searchText, setSearchText] = useState('');
/* 加载模型 */
const { isFetching } = useQuery(['loadModels'], () => loadMyModels(false));
const onclickCreateModel = useCallback(async () => {
setIsLoading(true);
try {
const id = await postCreateModel({
name: `AI应用${myApps.length + 1}`
});
toast({
title: '创建成功',
status: 'success'
});
refreshModel.freshMyModels();
router.push(`/model?modelId=${id}`);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : err.message || '出现了意外',
status: 'error'
});
}
setIsLoading(false);
}, [myApps.length, refreshModel, router, setIsLoading, toast]);
const currentModels = useMemo(() => {
const map = {
[MyModelsTypeEnum.my]: {
list: myApps.filter((item) => new RegExp(searchText, 'ig').test(item.name + item.intro)),
emptyText: '还没有 AI 应用~\n快来创建一个吧'
},
[MyModelsTypeEnum.collection]: {
list: myCollectionApps.filter((item) =>
new RegExp(searchText, 'ig').test(item.name + item.intro)
),
emptyText: '收藏的 AI 应用为空~\n快去市场找一个吧'
}
};
return map[currentTab];
}, [currentTab, myCollectionApps, myApps, searchText]);
return (
<Flex
position={'relative'}
flexDirection={'column'}
w={'100%'}
h={'100%'}
bg={'white'}
borderRight={['', theme.borders.base]}
>
<Flex w={'90%'} mt={5} mb={3} mx={'auto'}>
<Flex flex={1} mr={2} position={'relative'} alignItems={'center'}>
<Input
h={'32px'}
placeholder="根据名字和介绍搜索 AI 应用"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
{searchText && (
<MyIcon
zIndex={10}
position={'absolute'}
right={3}
name={'closeSolid'}
w={'16px'}
h={'16px'}
color={'myGray.500'}
cursor={'pointer'}
onClick={() => setSearchText('')}
/>
)}
</Flex>
<Tooltip label={'新建一个AI应用'}>
<IconButton
h={'32px'}
icon={<AddIcon />}
aria-label={''}
variant={'base'}
onClick={onclickCreateModel}
/>
</Tooltip>
</Flex>
<Flex userSelect={'none'}>
<Box flex={1}></Box>
<Tabs
w={'130px'}
list={[
{ label: '我的', id: MyModelsTypeEnum.my },
{ label: '收藏', id: MyModelsTypeEnum.collection }
]}
activeId={currentTab}
size={'sm'}
onChange={(id: any) => setCurrentTab(id)}
/>
</Flex>
<Box flex={'1 0 0'} h={0} pl={[0, 2]} overflowY={'scroll'} userSelect={'none'}>
{currentModels.list.map((item) => (
<Flex
key={item._id}
position={'relative'}
alignItems={'center'}
p={3}
mb={[2, 0]}
cursor={'pointer'}
transition={'background-color .2s ease-in'}
borderRadius={['', 'md']}
borderBottom={['1px solid #f4f4f4', 'none']}
_hover={{
backgroundImage: ['', theme.lgColor.hoverBlueGradient]
}}
{...(modelId === item._id
? {
backgroundImage: `${theme.lgColor.activeBlueGradient} !important`
}
: {})}
onClick={() => {
if (item._id === modelId) return;
router.push(`/model?modelId=${item._id}`);
}}
>
<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>
</Flex>
))}
{!isFetching && currentModels.list.length === 0 && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'30vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
{currentModels.emptyText}
</Box>
</Flex>
)}
</Box>
<Loading loading={isFetching} fixed={false} />
</Flex>
);
};
export default ModelList;

View File

@@ -1,85 +0,0 @@
import React, { useEffect, useState } from 'react';
import { Box, Divider, Flex, useTheme, Button, Skeleton, useDisclosure } from '@chakra-ui/react';
import { useCopyData } from '@/utils/tools';
import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
ssr: false
});
const API = ({ modelId }: { modelId: string }) => {
const theme = useTheme();
const { copyData } = useCopyData();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi');
const {
isOpen: isOpenAPIModal,
onOpen: onOpenAPIModal,
onClose: onCloseAPIModal
} = useDisclosure();
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
setBaseUrl(`${location.origin}/api/openapi`);
}, []);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Box display={['none', 'flex']} px={5} alignItems={'center'}>
<Box flex={1}>
AppId:
<Box
as={'span'}
ml={2}
fontWeight={'bold'}
cursor={'pointer'}
onClick={() => copyData(modelId, '已复制 AppId')}
>
{modelId}
</Box>
</Box>
<Flex
bg={'myWhite.600'}
py={2}
px={4}
borderRadius={'md'}
cursor={'pointer'}
onClick={() => copyData(baseUrl, '已复制 API 地址')}
>
<Box border={theme.borders.md} px={2} borderRadius={'md'} fontSize={'sm'}>
API服务器
</Box>
<Box ml={2} color={'myGray.900'} fontSize={['sm', 'md']}>
{baseUrl}
</Box>
</Flex>
<Button
ml={3}
leftIcon={<MyIcon name={'apikey'} w={'16px'} color={''} />}
variant={'base'}
onClick={onOpenAPIModal}
>
API
</Button>
</Box>
<Divider mt={3} />
<Box flex={1}>
<Skeleton h="100%" isLoaded={isLoaded} fadeDuration={2}>
<iframe
style={{
width: '100%',
height: '100%'
}}
src="https://kjqvjse66l.feishu.cn/docx/DmLedTWtUoNGX8xui9ocdUEjnNh"
frameBorder="0"
onLoad={() => setIsLoaded(true)}
onError={() => setIsLoaded(true)}
/>
</Skeleton>
</Box>
{isOpenAPIModal && <APIKeyModal onClose={onCloseAPIModal} />}
</Flex>
);
};
export default API;

View File

@@ -1,394 +0,0 @@
import React, { useState, useCallback } from 'react';
import { useRouter } from 'next/router';
import {
Card,
Flex,
Box,
Button,
useDisclosure,
Modal,
ModalOverlay,
ModalContent,
ModalBody,
ModalHeader,
ModalFooter,
ModalCloseButton,
Grid,
useTheme,
IconButton,
Tooltip,
Textarea
} from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
import { useQuery } from '@tanstack/react-query';
import Avatar from '@/components/Avatar';
import { AddIcon, DeleteIcon, QuestionOutlineIcon } from '@chakra-ui/icons';
import { putAppById } from '@/api/app';
import { useToast } from '@/hooks/useToast';
import { useLoading } from '@/hooks/useLoading';
import { useForm } from 'react-hook-form';
import MyIcon from '@/components/Icon';
import MySlider from '@/components/Slider';
const Kb = ({ modelId }: { modelId: string }) => {
const theme = useTheme();
const router = useRouter();
const { toast } = useToast();
const { appDetail, loadKbList, loadAppDetail } = useUserStore();
const { Loading, setIsLoading } = useLoading();
const [selectedIdList, setSelectedIdList] = useState<string[]>([]);
const [refresh, setRefresh] = useState(false);
const { register, reset, getValues, setValue } = useForm({
defaultValues: {
searchSimilarity: appDetail.chat.searchSimilarity,
searchLimit: appDetail.chat.searchLimit,
searchEmptyText: appDetail.chat.searchEmptyText
}
});
const {
isOpen: isOpenKbSelect,
onOpen: onOpenKbSelect,
onClose: onCloseKbSelect
} = useDisclosure();
const {
isOpen: isOpenEditParams,
onOpen: onOpenEditParams,
onClose: onCloseEditParams
} = useDisclosure();
const onchangeKb = useCallback(
async (
data: {
relatedKbs?: string[];
searchSimilarity?: number;
searchLimit?: number;
searchEmptyText?: string;
} = {}
) => {
setIsLoading(true);
try {
await putAppById(modelId, {
chat: {
...appDetail.chat,
...data
}
});
loadAppDetail(modelId, true);
} catch (err: any) {
toast({
title: err?.message || '更新失败',
status: 'error'
});
}
setIsLoading(false);
},
[setIsLoading, modelId, appDetail.chat, loadAppDetail, toast]
);
// init kb select list
const { isLoading, data: kbList = [] } = useQuery(['loadKbList'], () => loadKbList());
return (
<Box position={'relative'} px={5} minH={'50vh'}>
<Box fontWeight={'bold'}>({appDetail.chat?.relatedKbs.length})</Box>
{(() => {
const kbs =
appDetail.chat?.relatedKbs
?.map((id) => kbList.find((kb) => kb._id === id))
.filter((item) => item) || [];
return (
<Grid
mt={2}
gridTemplateColumns={[
'repeat(1,1fr)',
'repeat(2,1fr)',
'repeat(3,1fr)',
'repeat(4,1fr)'
]}
gridGap={[3, 4]}
>
<Card
p={3}
border={theme.borders.base}
boxShadow={'sm'}
cursor={'pointer'}
bg={'myGray.100'}
_hover={{
bg: 'white',
color: 'myBlue.800'
}}
onClick={() => {
reset({
searchSimilarity: appDetail.chat.searchSimilarity,
searchLimit: appDetail.chat.searchLimit,
searchEmptyText: appDetail.chat.searchEmptyText
});
onOpenEditParams();
}}
>
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
<IconButton
mr={2}
size={'sm'}
borderRadius={'lg'}
icon={<MyIcon name={'edit'} w={'14px'} color={'myGray.600'} />}
aria-label={''}
variant={'base'}
/>
</Flex>
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
: {appDetail.chat.searchSimilarity}, :{' '}
{appDetail.chat.searchLimit}, :{' '}
{appDetail.chat.searchEmptyText !== '' ? 'true' : 'false'}
</Flex>
</Card>
<Card
p={3}
border={theme.borders.base}
boxShadow={'sm'}
cursor={'pointer'}
bg={'myGray.100'}
_hover={{
bg: 'white',
color: 'myBlue.800'
}}
onClick={() => {
setSelectedIdList(
appDetail.chat?.relatedKbs ? [...appDetail.chat?.relatedKbs] : []
);
onOpenKbSelect();
}}
>
<Flex alignItems={'center'} h={'38px'} fontWeight={'bold'}>
<IconButton
mr={2}
size={'sm'}
borderRadius={'lg'}
icon={<AddIcon />}
aria-label={''}
variant={'base'}
/>
</Flex>
<Flex mt={3} h={'30px'} color={'myGray.600'} fontSize={'sm'}>
AI
</Flex>
</Card>
{kbs.map((item) =>
item ? (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
_hover={{
boxShadow: 'lg',
'& .detailBtn': {
display: 'block'
},
'& .delete': {
display: 'block'
}
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['26px', '32px', '38px']}></Avatar>
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
{item.name}
</Box>
</Flex>
<Flex mt={3} alignItems={'flex-end'} justifyContent={'flex-end'} h={'30px'}>
<Button
mr={3}
className="detailBtn"
display={['flex', 'none']}
variant={'base'}
size={'sm'}
onClick={() => router.push(`/kb?kbId=${item._id}`)}
>
</Button>
<IconButton
className="delete"
display={['flex', 'none']}
icon={<DeleteIcon />}
variant={'outline'}
aria-label={'delete'}
size={'sm'}
_hover={{ color: 'red.600' }}
onClick={() => {
const ids = appDetail.chat?.relatedKbs
? [...appDetail.chat.relatedKbs]
: [];
const i = ids.findIndex((id) => id === item._id);
ids.splice(i, 1);
onchangeKb({ relatedKbs: ids });
}}
/>
</Flex>
</Card>
) : null
)}
</Grid>
);
})()}
{/* select kb modal */}
<Modal isOpen={isOpenKbSelect} onClose={onCloseKbSelect}>
<ModalOverlay />
<ModalContent
display={'flex'}
flexDirection={'column'}
w={'800px'}
maxW={'90vw'}
h={['90vh', 'auto']}
>
<ModalHeader>({selectedIdList.length})</ModalHeader>
<ModalCloseButton />
<ModalBody
flex={['1 0 0', '0 0 auto']}
maxH={'80vh'}
overflowY={'auto'}
display={'grid'}
gridTemplateColumns={['repeat(1,1fr)', 'repeat(2,1fr)', 'repeat(3,1fr)']}
gridGap={3}
>
{kbList.map((item) => (
<Card
key={item._id}
p={3}
border={theme.borders.base}
boxShadow={'sm'}
h={'80px'}
cursor={'pointer'}
order={appDetail.chat?.relatedKbs?.includes(item._id) ? 0 : 1}
_hover={{
boxShadow: 'md'
}}
{...(selectedIdList.includes(item._id)
? {
bg: 'myBlue.300'
}
: {})}
onClick={() => {
let ids = [...selectedIdList];
if (!selectedIdList.includes(item._id)) {
ids = ids.concat(item._id);
} else {
const i = ids.findIndex((id) => id === item._id);
ids.splice(i, 1);
}
ids = ids.filter((id) => kbList.find((item) => item._id === id));
setSelectedIdList(ids);
}}
>
<Flex alignItems={'center'} h={'38px'}>
<Avatar src={item.avatar} w={['24px', '28px', '32px']}></Avatar>
<Box ml={3} fontWeight={'bold'} fontSize={['md', 'lg', 'xl']}>
{item.name}
</Box>
</Flex>
</Card>
))}
</ModalBody>
<ModalFooter>
<Button
onClick={() => {
onCloseKbSelect();
onchangeKb({ relatedKbs: selectedIdList });
}}
>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
{/* edit mode */}
<Modal isOpen={isOpenEditParams} onClose={onCloseEditParams}>
<ModalOverlay />
<ModalContent display={'flex'} flexDirection={'column'} w={'600px'} maxW={'90vw'}>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex pt={3} pb={5}>
<Box flex={'0 0 100px'}>
<Tooltip label={'高相似度推荐0.8及以上。'}>
<QuestionOutlineIcon ml={1} />
</Tooltip>
</Box>
<MySlider
markList={[
{ label: '0', value: 0 },
{ label: '1', value: 1 }
]}
min={0}
max={1}
step={0.01}
value={getValues('searchSimilarity')}
onChange={(val) => {
setValue('searchSimilarity', val);
setRefresh(!refresh);
}}
/>
</Flex>
<Flex py={8}>
<Box flex={'0 0 100px'}></Box>
<Box flex={1}>
<MySlider
markList={[
{ label: '1', value: 1 },
{ label: '20', value: 20 }
]}
min={1}
max={20}
value={getValues('searchLimit')}
onChange={(val) => {
setValue('searchLimit', val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex pt={3}>
<Box flex={'0 0 100px'}></Box>
<Box flex={1}>
<Textarea
rows={5}
maxLength={500}
placeholder={
'若填写该内容,没有搜索到对应内容时,将直接回复填写的内容。\n为了连贯上下文FastGpt 会取部分上一个聊天的搜索记录作为补充,因此在连续对话时,该功能可能会失效。'
}
{...register('searchEmptyText')}
></Textarea>
</Box>
</Flex>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onCloseEditParams}>
</Button>
<Button
onClick={() => {
onCloseEditParams();
onchangeKb({
searchSimilarity: getValues('searchSimilarity'),
searchLimit: getValues('searchLimit'),
searchEmptyText: getValues('searchEmptyText')
});
}}
>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
<Loading loading={isLoading} fixed={false} />
</Box>
);
};
export default Kb;

View File

@@ -1,281 +0,0 @@
import React, { useCallback, useState } from 'react';
import {
Flex,
Box,
Tooltip,
Button,
TableContainer,
Table,
Thead,
Tr,
Th,
Td,
Tbody,
useDisclosure,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
FormControl,
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
SliderMark,
Input
} from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyIcon from '@/components/Icon';
import { useToast } from '@/hooks/useToast';
import { useLoading } from '@/hooks/useLoading';
import { useQuery } from '@tanstack/react-query';
import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat';
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
import { useForm } from 'react-hook-form';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/model';
const Share = ({ modelId }: { modelId: string }) => {
const { toast } = useToast();
const { Loading, setIsLoading } = useLoading();
const { copyData } = useCopyData();
const {
isOpen: isOpenCreateShareChat,
onOpen: onOpenCreateShareChat,
onClose: onCloseCreateShareChat
} = useDisclosure();
const {
register: registerShareChat,
getValues: getShareChatValues,
setValue: setShareChatValues,
handleSubmit: submitShareChat,
reset: resetShareChat
} = useForm({
defaultValues: defaultShareChat
});
const [refresh, setRefresh] = useState(false);
const {
isFetching,
data: shareChatList = [],
refetch: refetchShareChatList
} = useQuery(['initShareChatList', modelId], () => getShareChatList(modelId));
const onclickCreateShareChat = useCallback(
async (e: ShareChatEditType) => {
try {
setIsLoading(true);
const id = await createShareChat({
...e,
modelId
});
onCloseCreateShareChat();
refetchShareChatList();
const url = `对话地址为:${location.origin}/chat/share?shareId=${id}
${e.password ? `密码为: ${e.password}` : ''}`;
copyData(url, '已复制分享地址');
resetShareChat(defaultShareChat);
} catch (err) {
toast({
title: getErrText(err, '创建分享链接异常'),
status: 'warning'
});
console.log(err);
}
setIsLoading(false);
},
[
copyData,
modelId,
onCloseCreateShareChat,
refetchShareChatList,
resetShareChat,
setIsLoading,
toast
]
);
// format share used token
const formatTokens = (tokens: number) => {
if (tokens < 10000) return tokens;
return `${(tokens / 10000).toFixed(2)}`;
};
return (
<Box position={'relative'} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<Tooltip label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。">
<QuestionOutlineIcon ml={1} />
</Tooltip>
</Box>
<Button
variant={'base'}
colorScheme={'myBlue'}
size={['sm', 'md']}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'}>
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th>tokens消耗</Th>
<Th>使</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
<Td>{item.maxContext}</Td>
<Td>{formatTokens(item.tokens)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td>
<Flex>
<MyIcon
mr={3}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item._id}`;
copyData(url, '已复制分享地址');
}}
/>
<MyIcon
name="delete"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'red' }}
onClick={async () => {
setIsLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setIsLoading(false);
}}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{shareChatList.length === 0 && !isFetching && (
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
<Box mt={2} color={'myGray.500'}>
</Box>
</Flex>
)}
{/* create shareChat modal */}
<Modal isOpen={isOpenCreateShareChat} onClose={onCloseCreateShareChat}>
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl>
<Flex alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
:
</Box>
<Input
placeholder="记录名字,仅用于展示"
maxLength={20}
{...registerShareChat('name', {
required: '记录名称不能为空'
})}
/>
</Flex>
</FormControl>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
:
</Box>
<Input placeholder={'不设置密码,可直接访问'} {...registerShareChat('password')} />
</Flex>
<Box fontSize={'xs'} ml={'60px'} color={'myGray.600'}>
</Box>
</FormControl>
<FormControl mt={9}>
<Flex alignItems={'center'}>
<Box flex={'0 0 120px'} w={0}>
</Box>
<Slider
aria-label="slider-ex-1"
min={1}
max={20}
step={1}
value={getShareChatValues('maxContext')}
onChange={(e) => {
setShareChatValues('maxContext', e);
setRefresh(!refresh);
}}
>
<SliderMark
value={getShareChatValues('maxContext')}
textAlign="center"
bg="myBlue.600"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
>
{getShareChatValues('maxContext')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack bg={'myBlue.700'} />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
</Button>
<Button onClick={submitShareChat(onclickCreateShareChat)}></Button>
</ModalFooter>
</ModalContent>
</Modal>
<Loading loading={isFetching} fixed={false} />
</Box>
);
};
export default Share;

View File

@@ -1,102 +0,0 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
import { useGlobalStore } from '@/store/global';
import dynamic from 'next/dynamic';
import Tabs from '@/components/Tabs';
import Settings from './components/Settings';
import { defaultApp } from '@/constants/model';
const Kb = dynamic(() => import('./components/Kb'), {
ssr: false
});
const Share = dynamic(() => import('./components/Share'), {
ssr: false
});
const API = dynamic(() => import('./components/API'), {
ssr: false
});
enum TabEnum {
'settings' = 'settings',
'kb' = 'kb',
'share' = 'share',
'API' = 'API'
}
const ModelDetail = ({ modelId }: { modelId: string }) => {
const router = useRouter();
const { isPc } = useGlobalStore();
const { appDetail = defaultApp, userInfo } = useUserStore();
const [currentTab, setCurrentTab] = useState<`${TabEnum}`>(TabEnum.settings);
const isOwner = useMemo(
() => appDetail.userId === userInfo?._id,
[appDetail.userId, userInfo?._id]
);
useEffect(() => {
window.onbeforeunload = (e) => {
e.preventDefault();
e.returnValue = '内容已修改,确认离开页面吗?';
};
return () => {
window.onbeforeunload = null;
};
}, [router]);
useEffect(() => {
setCurrentTab(TabEnum.settings);
}, [modelId]);
return (
<Flex
flexDirection={'column'}
h={'100%'}
maxW={'100vw'}
pt={4}
overflow={'overlay'}
position={'relative'}
bg={'white'}
>
{/* 头部 */}
<Box textAlign={['center', 'left']} px={5} mb={4}>
<Box className="textlg" display={['block', 'none']} fontSize={'3xl'} fontWeight={'bold'}>
{appDetail.name}
</Box>
<Tabs
mx={['auto', '0']}
mt={2}
w={['300px', '360px']}
list={[
{ label: '配置', id: TabEnum.settings },
...(isOwner ? [{ label: '知识库', id: TabEnum.kb }] : []),
{ label: '分享', id: TabEnum.share },
{ label: 'API', id: TabEnum.API },
{ label: '立即对话', id: 'startChat' }
]}
size={isPc ? 'md' : 'sm'}
activeId={currentTab}
onChange={(e: any) => {
if (e === 'startChat') {
router.push(`/chat?modelId=${modelId}`);
} else {
setCurrentTab(e);
}
}}
/>
</Box>
<Box flex={1}>
{currentTab === TabEnum.settings && <Settings modelId={modelId} />}
{currentTab === TabEnum.kb && <Kb modelId={modelId} />}
{currentTab === TabEnum.API && <API modelId={modelId} />}
{currentTab === TabEnum.share && <Share modelId={modelId} />}
</Box>
</Flex>
);
};
export default ModelDetail;

View File

@@ -1,44 +0,0 @@
import React, { useEffect } from 'react';
import { Box, Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import ModelList from './components/ModelList';
import dynamic from 'next/dynamic';
import { useUserStore } from '@/store/user';
import { useGlobalStore } from '@/store/global';
import Loading from '@/components/Loading';
import SideBar from '@/components/SideBar';
const ModelDetail = dynamic(() => import('./components/detail/index'), {
loading: () => <Loading fixed={false} />,
ssr: false
});
const Model = () => {
const router = useRouter();
const { modelId = '' } = router.query as { modelId: string };
const { isPc } = useGlobalStore();
const { lastModelId } = useUserStore();
// redirect modelId
useEffect(() => {
if (isPc && !modelId && lastModelId) {
router.replace(`/model?modelId=${lastModelId}`);
}
}, [isPc, lastModelId, modelId, router]);
return (
<Flex h={'100%'} position={'relative'} overflow={'hidden'}>
{/* 模型列表 */}
{(isPc || !modelId) && (
<SideBar w={['100%', '0 0 250px', '0 0 270px', '0 0 290px']}>
<ModelList modelId={modelId} />
</SideBar>
)}
<Box flex={1} h={'100%'} position={'relative'}>
{modelId && <ModelDetail modelId={modelId} />}
</Box>
</Flex>
);
};
export default Model;

View File

@@ -1,5 +1,5 @@
import { Schema, model, models, Model as MongoModel } from 'mongoose';
import { AppSchema as ModelType } from '@/types/mongoSchema';
import { Schema, model, models, Model } from 'mongoose';
import { AppSchema as AppType } from '@/types/mongoSchema';
import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
const AppSchema = new Schema({
@@ -105,4 +105,4 @@ try {
console.log(error);
}
export const Model: MongoModel<ModelType> = models['model'] || model('model', AppSchema);
export const App: Model<AppType> = models['model'] || model('model', AppSchema);

View File

@@ -0,0 +1,18 @@
import { Schema, model, models, Model } from 'mongoose';
import { ChatSchema as ChatType } from '@/types/mongoSchema';
import { ChatRoleMap } from '@/constants/chat';
const InstallAppSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'user',
required: true
},
modelId: {
type: Schema.Types.ObjectId,
ref: 'model',
required: true
}
});
export const InstallApp: Model<ChatType> = models['installApp'] || model('chat', InstallAppSchema);

View File

@@ -55,7 +55,7 @@ export async function connectToDatabase(): Promise<void> {
export * from './models/authCode';
export * from './models/chat';
export * from './models/model';
export * from './models/app';
export * from './models/user';
export * from './models/bill';
export * from './models/pay';

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest } from 'next';
import jwt from 'jsonwebtoken';
import Cookie from 'cookie';
import { Chat, Model, OpenApi, User, ShareChat, KB } from '../mongo';
import { Chat, App, OpenApi, User, ShareChat, KB } from '../mongo';
import type { AppSchema } from '@/types/mongoSchema';
import type { ChatItemType } from '@/types/chat';
import mongoose from 'mongoose';
@@ -218,7 +218,7 @@ export const authApp = async ({
reserveDetail?: boolean; // focus reserve detail
}) => {
// 获取 model 数据
const app = await Model.findById<AppSchema>(appId);
const app = await App.findById<AppSchema>(appId);
if (!app) {
return Promise.reject('模型不存在');
}

View File

@@ -6,7 +6,7 @@ import { getMyModels, getModelById } from '@/api/app';
import { formatPrice } from '@/utils/user';
import { getTokenLogin } from '@/api/user';
import { defaultApp } from '@/constants/model';
import { ModelListItemType } from '@/types/model';
import { AppListItemType } from '@/types/app';
import { KbItemType } from '@/types/plugin';
import { getKbList, getKbById } from '@/api/plugins/kb';
import { defaultKbDetail } from '@/constants/kb';
@@ -20,8 +20,8 @@ type State = {
// model
lastModelId: string;
setLastModelId: (id: string) => void;
myApps: ModelListItemType[];
myCollectionApps: ModelListItemType[];
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
loadMyModels: (init?: boolean) => Promise<null>;
appDetail: AppSchema;
loadAppDetail: (id: string, init?: boolean) => Promise<AppSchema>;

View File

@@ -2,6 +2,40 @@ import { FlowModuleTypeEnum } from '@/constants/flow';
import { XYPosition } from 'reactflow';
import { AppModuleItemTypeEnum, ModulesInputItemTypeEnum } from '../constants/app';
import type { FlowInputItemType, FlowOutputItemType } from './flow';
import type { AppSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model';
export type AppListItemType = {
_id: string;
name: string;
avatar: string;
intro: string;
};
export interface AppUpdateParams {
name?: string;
avatar?: string;
intro?: string;
chat?: AppSchema['chat'];
share?: AppSchema['share'];
modules?: AppSchema['modules'];
}
export interface ShareAppItem {
_id: string;
avatar: string;
name: string;
intro: string;
userId: string;
share: AppSchema['share'];
isCollection: boolean;
}
export type ShareChatEditType = {
name: string;
password: string;
maxContext: number;
};
/* agent */
/* question classify */

View File

@@ -1,34 +0,0 @@
import type { AppSchema, kbSchema } from './mongoSchema';
import { ChatModelType } from '@/constants/model';
export type ModelListItemType = {
_id: string;
name: string;
avatar: string;
intro: string;
};
export interface ModelUpdateParams {
name?: string;
avatar?: string;
intro?: string;
chat?: AppSchema['chat'];
share?: AppSchema['share'];
modules?: AppSchema['modules'];
}
export interface ShareModelItem {
_id: string;
avatar: string;
name: string;
intro: string;
userId: string;
share: AppSchema['share'];
isCollection: boolean;
}
export type ShareChatEditType = {
name: string;
password: string;
maxContext: number;
};

View File

@@ -57,17 +57,11 @@ export interface AppSchema {
modules: AppModuleItemType[];
}
export interface ModelPopulate extends AppSchema {
userId: UserModelSchema;
}
export interface CollectionSchema {
modelId: string;
userId: string;
}
export type ModelDataType = 0 | 1;
export interface TrainingDataSchema {
_id: string;
userId: string;