feat: share unlogin.perf: link format and model ui

This commit is contained in:
archer 2023-05-19 10:26:30 +08:00
parent a62a9c4067
commit 246ee973ec
No known key found for this signature in database
GPG Key ID: 569A5660D2379E28
15 changed files with 283 additions and 394 deletions

View File

@ -51,7 +51,6 @@
"react-syntax-highlighter": "^15.5.0",
"redis": "^4.6.5",
"rehype-katex": "^6.0.2",
"rehype-raw": "^6.1.1",
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"sass": "^1.58.3",

View File

@ -59,7 +59,6 @@ specifiers:
react-syntax-highlighter: ^15.5.0
redis: ^4.6.5
rehype-katex: ^6.0.2
rehype-raw: ^6.1.1
remark-gfm: ^3.0.1
remark-math: ^5.1.1
sass: ^1.58.3
@ -110,7 +109,6 @@ dependencies:
react-syntax-highlighter: registry.npmmirror.com/react-syntax-highlighter/15.5.0_react@18.2.0
redis: registry.npmmirror.com/redis/4.6.5
rehype-katex: registry.npmmirror.com/rehype-katex/6.0.2
rehype-raw: 6.1.1
remark-gfm: registry.npmmirror.com/remark-gfm/3.0.1
remark-math: registry.npmmirror.com/remark-math/5.1.1
sass: registry.npmmirror.com/sass/1.58.3
@ -302,31 +300,15 @@ packages:
'@types/unist': 2.0.6
dev: false
/@types/parse5/6.0.3:
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
dev: false
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/bail/2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
dev: false
/comma-separated-tokens/2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
dev: false
/cookie/0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
dev: false
/extend/3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -341,91 +323,6 @@ packages:
dev: false
optional: true
/hast-util-from-parse5/7.1.2:
resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==}
dependencies:
'@types/hast': 2.3.4
'@types/unist': 2.0.6
hastscript: 7.2.0
property-information: 6.2.0
vfile: 5.3.7
vfile-location: 4.1.0
web-namespaces: 2.0.1
dev: false
/hast-util-parse-selector/3.1.1:
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
dependencies:
'@types/hast': 2.3.4
dev: false
/hast-util-raw/7.2.3:
resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==}
dependencies:
'@types/hast': 2.3.4
'@types/parse5': 6.0.3
hast-util-from-parse5: 7.1.2
hast-util-to-parse5: 7.1.0
html-void-elements: 2.0.1
parse5: 6.0.1
unist-util-position: 4.0.4
unist-util-visit: 4.1.2
vfile: 5.3.7
web-namespaces: 2.0.1
zwitch: 2.0.4
dev: false
/hast-util-to-parse5/7.1.0:
resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==}
dependencies:
'@types/hast': 2.3.4
comma-separated-tokens: 2.0.3
property-information: 6.2.0
space-separated-tokens: 2.0.2
web-namespaces: 2.0.1
zwitch: 2.0.4
dev: false
/hastscript/7.2.0:
resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
dependencies:
'@types/hast': 2.3.4
comma-separated-tokens: 2.0.3
hast-util-parse-selector: 3.1.1
property-information: 6.2.0
space-separated-tokens: 2.0.2
dev: false
/html-void-elements/2.0.1:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
dev: false
/is-buffer/2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
dev: false
/is-plain-obj/4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
dev: false
/parse5/6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
dev: false
/property-information/6.2.0:
resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==}
dev: false
/rehype-raw/6.1.1:
resolution: {integrity: sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ==}
dependencies:
'@types/hast': 2.3.4
hast-util-raw: 7.2.3
unified: 10.1.2
dev: false
/saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
engines: {node: '>=6'}
@ -442,88 +339,6 @@ packages:
dev: false
optional: true
/space-separated-tokens/2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
dev: false
/trough/2.1.0:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: false
/unified/10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
'@types/unist': 2.0.6
bail: 2.0.2
extend: 3.0.2
is-buffer: 2.0.5
is-plain-obj: 4.1.0
trough: 2.1.0
vfile: 5.3.7
dev: false
/unist-util-is/5.2.0:
resolution: {integrity: sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ==}
dev: false
/unist-util-position/4.0.4:
resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-stringify-position/3.0.3:
resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/5.1.3:
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.0
dev: false
/unist-util-visit/4.1.2:
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.0
unist-util-visit-parents: 5.1.3
dev: false
/vfile-location/4.1.0:
resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==}
dependencies:
'@types/unist': 2.0.6
vfile: 5.3.7
dev: false
/vfile-message/3.1.4:
resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.3
dev: false
/vfile/5.3.7:
resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==}
dependencies:
'@types/unist': 2.0.6
is-buffer: 2.0.5
unist-util-stringify-position: 3.0.3
vfile-message: 3.1.4
dev: false
/web-namespaces/2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
dev: false
/zwitch/2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
registry.npmmirror.com/@alicloud/credentials/2.2.6:
resolution: {integrity: sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz}
name: '@alicloud/credentials'

View File

@ -1,4 +1,3 @@
### Fast GPT V3.6
### Fast GPT V3.7
- 新增 - 分享免登录聊天框。可以直接为模型生成一个分享链接,其他人可以通过这个链接直接使用对话。
- 优化 - UI 细节。
- 新增 - 知识库与 AI 助手对多对关系,一个知识库可以被多个 AI 助手关联,一个 AI 助手可以关联多个知识库。

View File

@ -9,6 +9,8 @@ export type KbUpdateParams = { id: string; name: string; tags: string; avatar: s
/* knowledge base */
export const getKbList = () => GET<KbItemType[]>(`/plugins/kb/list`);
export const getKbById = (id: string) => GET<KbItemType>(`/plugins/kb/detail?id=${id}`);
export const postCreateKb = (data: { name: string }) => POST<string>(`/plugins/kb/create`, data);
export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, data);

View File

@ -2,12 +2,11 @@ import React, { memo, useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { Box, Flex, useColorModeValue } from '@chakra-ui/react';
import { useCopyData, formatLinkTextToHtml } from '@/utils/tools';
import { useCopyData, formatLinkText } from '@/utils/tools';
import Icon from '@/components/Icon';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import rehypeKatex from 'rehype-katex';
import rehypeRaw from 'rehype-raw';
import 'katex/dist/katex.min.css';
import styles from './index.module.scss';
@ -25,7 +24,7 @@ const Markdown = ({
const { copyData } = useCopyData();
const formatSource = useMemo(() => {
return formatLink ? formatLinkTextToHtml(source) : source;
return formatLink ? formatLinkText(source) : source;
}, [source, formatLink]);
return (
@ -34,7 +33,7 @@ const Markdown = ({
isChatting ? (source === '' ? styles.waitingAnimation : styles.animation) : ''
}`}
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeRaw, remarkGfm, rehypeKatex]}
rehypePlugins={[remarkGfm, rehypeKatex]}
components={{
pre: 'div',
code({ node, inline, className, children, ...props }) {

View File

@ -33,7 +33,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
let startTime = Date.now();
const { model, showModelDetail, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
const { model, userOpenAiKey, systemAuthKey, userId } = await authShareChat({
shareId,
password
});

View File

@ -16,7 +16,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const userId = await authToken(req);
await authModel({
modelId,
userId
userId,
authOwner: false
});
const { _id } = await ShareChat.create({

View File

@ -36,7 +36,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 校验使用权限
const { model } = await authModel({
modelId: shareChat.modelId,
userId: String(shareChat.userId)
userId: String(shareChat.userId),
authOwner: false
});
jsonRes<InitShareChatResponse>(res, {

View File

@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, KB } from '@/service/mongo';
import { authToken } from '@/service/utils/auth';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { id } = req.query as {
id: string;
};
if (!id) {
throw new Error('缺少参数');
}
// 凭证校验
const userId = await authToken(req);
await connectToDatabase();
const data = await KB.findOne({
_id: id,
userId
});
if (!data) {
throw new Error('kb is not exist');
}
jsonRes(res, {
data: {
_id: data._id,
avatar: data.avatar,
name: data.name,
userId: data.userId,
updateTime: data.updateTime,
tags: data.tags.join(' ')
}
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
}

View File

@ -24,18 +24,18 @@ import { useSelectFile } from '@/hooks/useSelectFile';
import { useConfirm } from '@/hooks/useConfirm';
import { compressImg } from '@/utils/file';
import DataCard from './DataCard';
import { getErrText } from '@/utils/tools';
const Detail = ({ kbId }: { kbId: string }) => {
const { toast } = useToast();
const router = useRouter();
const InputRef = useRef<HTMLInputElement>(null);
const { setLastKbId, KbDetail, getKbDetail, loadKbList, myKbList } = useUserStore();
const { Loading, setIsLoading } = useLoading();
const { setLastKbId, kbDetail, getKbDetail, loadKbList, myKbList } = useUserStore();
const [btnLoading, setBtnLoading] = useState(false);
const [refresh, setRefresh] = useState(false);
const { getValues, formState, setValue, reset, register, handleSubmit } = useForm<KbItemType>({
defaultValues: KbDetail
defaultValues: kbDetail
});
const { openConfirm, ConfirmChild } = useConfirm({
content: '确认删除该知识库?数据将无法恢复,请确认!'
@ -46,7 +46,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
multiple: false
});
const { isLoading } = useQuery([kbId, myKbList], () => getKbDetail(kbId), {
useQuery([kbId, myKbList], () => getKbDetail(kbId), {
onSuccess(res) {
kbId && setLastKbId(kbId);
if (res) {
@ -58,17 +58,18 @@ const Detail = ({ kbId }: { kbId: string }) => {
},
onError(err: any) {
toast({
title: err?.message || '获取AI助手异常',
title: getErrText(err, '获取AI助手异常'),
status: 'error'
});
loadKbList(true);
setLastKbId('');
router.replace('/model');
router.replace(`/kb?kbId=${myKbList[0]?._id || ''}`);
}
});
/* 点击删除 */
const onclickDelKb = useCallback(async () => {
setIsLoading(true);
setBtnLoading(true);
try {
await delKbById(kbId);
toast({
@ -83,8 +84,8 @@ const Detail = ({ kbId }: { kbId: string }) => {
status: 'error'
});
}
setIsLoading(false);
}, [setIsLoading, kbId, toast, router, myKbList, loadKbList]);
setBtnLoading(false);
}, [setBtnLoading, kbId, toast, router, myKbList, loadKbList]);
const saveSubmitSuccess = useCallback(
async (data: KbItemType) => {
@ -155,7 +156,7 @@ const Detail = ({ kbId }: { kbId: string }) => {
<Box fontWeight={'bold'} fontSize={'2xl'} flex={1}>
</Box>
{KbDetail._id && (
{kbDetail._id && (
<>
<Button
isLoading={btnLoading}
@ -237,7 +238,6 @@ const Detail = ({ kbId }: { kbId: string }) => {
</Card>
<File onSelect={onSelectFile} />
<ConfirmChild />
<Loading loading={isLoading} fixed={false} />
</Box>
);
};

View File

@ -27,7 +27,6 @@ import {
Table,
Thead,
Tbody,
Tfoot,
Tr,
Th,
Td,
@ -48,22 +47,24 @@ import { useRouter } from 'next/router';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/model';
import type { ModelSchema } from '@/types/mongoSchema';
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
import { formatTimeToChatTime, useCopyData, getErrText } from '@/utils/tools';
import MyIcon from '@/components/Icon';
import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import type { KbItemType } from '@/types/plugin';
const ModelEditForm = ({
formHooks,
isOwner,
canRead,
handleDelModel
}: {
formHooks: UseFormReturn<ModelSchema>;
isOwner: boolean;
canRead: boolean;
handleDelModel: () => void;
}) => {
const { modelId } = useRouter().query as { modelId: string };
const router = useRouter();
const { modelId } = router.query as { modelId: string };
const [refresh, setRefresh] = useState(false);
const { toast } = useToast();
const { setLoading } = useGlobalStore();
@ -112,7 +113,7 @@ const ModelEditForm = ({
setRefresh((state) => !state);
} catch (err: any) {
toast({
title: typeof err === 'string' ? err : '头像选择异常',
title: getErrText(err, '头像选择异常'),
status: 'warning'
});
}
@ -144,8 +145,12 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
copyData(url, '已复制分享地址');
resetShareChat(defaultShareChat);
} catch (error) {
console.log(error);
} catch (err) {
toast({
title: getErrText(err, '创建分享链接异常'),
status: 'warning'
});
console.log(err);
}
setLoading(false);
},
@ -156,7 +161,8 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
onCloseCreateShareChat,
refetchShareChatList,
resetShareChat,
setLoading
setLoading,
toast
]
);
@ -175,7 +181,13 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<>
{kbs.map((item) =>
item ? (
<Card key={item._id} p={3} mt={3}>
<Card
key={item._id}
p={3}
mt={3}
cursor={'pointer'}
onClick={() => router.push(`/kb?kbId=${item._id}`)}
>
<Flex alignItems={'center'}>
<Image
src={item.avatar}
@ -193,7 +205,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
)}
</>
);
}, [getValues, kbList]);
}, [getValues, kbList, router]);
return (
<>
@ -202,13 +214,13 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Box fontWeight={'bold'}></Box>
<Flex alignItems={'center'} mt={4}>
<Box flex={'0 0 80px'} w={0}>
modelId:
modelId
</Box>
<Box>{getValues('_id')}</Box>
<Box userSelect={'all'}>{getValues('_id')}</Box>
</Flex>
<Flex mt={4} alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Image
src={getValues('avatar') || '/icon/logo.png'}
@ -224,7 +236,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Input
isDisabled={!isOwner}
@ -237,7 +249,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Select
isDisabled={!isOwner}
@ -256,7 +268,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 80px'} w={0}>
:
</Box>
<Box>
{formatPrice(ChatModelMap[getValues('chat.chatModel')]?.price, 1000)}
@ -271,7 +283,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
{isOwner && (
<Flex mt={5} alignItems={'center'}>
<Box flex={'0 0 120px'}>AI和知识库</Box>
<Box flex={'0 0 100px'}>AI助手</Box>
<Button
colorScheme={'gray'}
variant={'outline'}
@ -284,80 +296,82 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
)}
</Card>
{/* model effect */}
<Card p={4}>
<Box fontWeight={'bold'}></Box>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
<Box as={'span'} mr={2}>
{canRead && (
<Card p={4}>
<Box fontWeight={'bold'}></Box>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 80px'} w={0}>
<Box as={'span'} mr={2}>
</Box>
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
<QuestionOutlineIcon />
</Tooltip>
</Box>
<Tooltip label={'温度越高,模型的发散能力越强;温度越低,内容越严谨。'}>
<QuestionOutlineIcon />
</Tooltip>
</Box>
<Slider
aria-label="slider-ex-1"
min={0}
max={10}
step={1}
value={getValues('chat.temperature')}
isDisabled={!isOwner}
onChange={(e) => {
setValue('chat.temperature', e);
setRefresh(!refresh);
}}
>
<SliderMark
<Slider
aria-label="slider-ex-1"
min={0}
max={10}
step={1}
value={getValues('chat.temperature')}
textAlign="center"
bg="myBlue.600"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
isDisabled={!isOwner}
onChange={(e) => {
setValue('chat.temperature', e);
setRefresh(!refresh);
}}
>
{getValues('chat.temperature')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack bg={'myBlue.700'} />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
{getValues('chat.relatedKbs').length > 0 && (
<Flex mt={4} alignItems={'center'}>
<Box mr={4} whiteSpace={'nowrap'}>
&emsp;
</Box>
<Select
isDisabled={!isOwner}
{...register('chat.searchMode', { required: '搜索模式不能为空' })}
>
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
<option key={key} value={key}>
{text}
</option>
))}
</Select>
</Flex>
)}
<SliderMark
value={getValues('chat.temperature')}
textAlign="center"
bg="myBlue.600"
color="white"
w={'18px'}
h={'18px'}
borderRadius={'100px'}
fontSize={'xs'}
transform={'translate(-50%, -200%)'}
>
{getValues('chat.temperature')}
</SliderMark>
<SliderTrack>
<SliderFilledTrack bg={'myBlue.700'} />
</SliderTrack>
<SliderThumb />
</Slider>
</Flex>
</FormControl>
{getValues('chat.relatedKbs').length > 0 && (
<Flex mt={4} alignItems={'center'}>
<Box mr={4} whiteSpace={'nowrap'}>
&emsp;
</Box>
<Select
isDisabled={!isOwner}
{...register('chat.searchMode', { required: '搜索模式不能为空' })}
>
{Object.entries(ModelVectorSearchModeMap).map(([key, { text }]) => (
<option key={key} value={key}>
{text}
</option>
))}
</Select>
</Flex>
)}
<Box mt={4}>
<Box mb={1}></Box>
<Textarea
rows={8}
maxLength={-1}
isDisabled={!isOwner}
placeholder={'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。'}
{...register('chat.systemPrompt')}
/>
</Box>
</Card>
<Box mt={4}>
<Box mb={1}></Box>
<Textarea
rows={8}
maxLength={-1}
isDisabled={!isOwner}
placeholder={'模型默认的 prompt 词,通过调整该内容,可以引导模型聊天方向。'}
{...register('chat.systemPrompt')}
/>
</Box>
</Card>
)}
{isOwner && (
<>
{/* model share setting */}
@ -419,90 +433,90 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
</Flex>
<RenderSelectedKbList />
</Card>
{/* shareChat */}
<Card p={4} gridColumnStart={1} gridColumnEnd={[2, 3]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<Tooltip label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。">
<QuestionOutlineIcon ml={1} />
</Tooltip>
Beta
</Box>
<Button
size={'sm'}
variant={'outline'}
colorScheme={'myBlue'}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={1} minH={'100px'}>
<Table variant={'simple'} w={'100%'}>
<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 () => {
setLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setLoading(false);
}}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Card>
</>
)}
{/* shareChat */}
<Card p={4} gridColumnStart={1} gridColumnEnd={[2, 3]}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>
<Tooltip label="可以直接分享该模型给其他用户去进行对话对方无需登录即可直接进行对话。注意这个功能会消耗你账号的tokens。请保管好链接和密码。">
<QuestionOutlineIcon ml={1} />
</Tooltip>
Beta
</Box>
<Button
size={'sm'}
variant={'outline'}
colorScheme={'myBlue'}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: '最多创建10组'
}
: {})}
onClick={onOpenCreateShareChat}
>
</Button>
</Flex>
<TableContainer mt={1} minH={'100px'}>
<Table variant={'simple'} w={'100%'}>
<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 () => {
setLoading(true);
try {
await delShareChatById(item._id);
refetchShareChatList();
} catch (error) {
console.log(error);
}
setLoading(false);
}}
/>
</Flex>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Card>
{/* create shareChat modal */}
<Modal isOpen={isOpenCreateShareChat} onClose={onCloseCreateShareChat}>
<ModalOverlay />

View File

@ -121,12 +121,17 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
);
useEffect(() => {
return () => {
saveUpdateModel();
window.onbeforeunload = (e) => {
e.preventDefault();
e.returnValue = '内容已修改,确认离开页面吗?';
};
}, []);
return canRead ? (
return () => {
window.onbeforeunload = null;
};
}, [router]);
return (
<Box h={'100%'} p={5} overflow={'overlay'} position={'relative'}>
{/* 头部 */}
<Card px={6} py={3}>
@ -197,14 +202,15 @@ const ModelDetail = ({ modelId, isPc }: { modelId: string; isPc: boolean }) => {
)}
</Card>
<Grid mt={5} gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={5}>
<ModelEditForm formHooks={formHooks} handleDelModel={handleDelModel} isOwner={isOwner} />
<ModelEditForm
formHooks={formHooks}
handleDelModel={handleDelModel}
isOwner={isOwner}
canRead={canRead}
/>
</Grid>
<Loading loading={isLoading} fixed={false} />
</Box>
) : (
<Box h={'100%'} p={5}>
</Box>
);
};

View File

@ -230,7 +230,8 @@ export const authShareChat = async ({
// 获取 model 数据
const { model, showModelDetail } = await authModel({
modelId,
userId
userId,
authOwner: false
});
// 获取 user 的 apiKey

View File

@ -8,7 +8,7 @@ import { getTokenLogin } from '@/api/user';
import { defaultModel } from '@/constants/model';
import { ModelListItemType } from '@/types/model';
import { KbItemType } from '@/types/plugin';
import { getKbList } from '@/api/plugins/kb';
import { getKbList, getKbById } from '@/api/plugins/kb';
import { defaultKbDetail } from '@/constants/kb';
import type { ModelSchema } from '@/types/mongoSchema';
@ -35,8 +35,8 @@ type State = {
setLastKbId: (id: string) => void;
myKbList: KbItemType[];
loadKbList: (init?: boolean) => Promise<KbItemType[]>;
KbDetail: KbItemType;
getKbDetail: (id: string) => KbItemType;
kbDetail: KbItemType;
getKbDetail: (id: string, init?: boolean) => Promise<KbItemType>;
};
export const useUserStore = create<State>()(
@ -130,12 +130,14 @@ export const useUserStore = create<State>()(
});
return res;
},
KbDetail: defaultKbDetail,
getKbDetail(id: string) {
const data = get().myKbList.find((item) => item._id === id) || defaultKbDetail;
kbDetail: defaultKbDetail,
async getKbDetail(id: string, init = false) {
if (id === get().kbDetail._id && !init) return get().kbDetail;
const data = await getKbById(id);
set((state) => {
state.KbDetail = data;
state.kbDetail = data;
});
return data;

View File

@ -115,8 +115,12 @@ export const voiceBroadcast = ({ text }: { text: string }) => {
};
};
export const formatLinkTextToHtml = (text: string) => {
export const formatLinkText = (text: string) => {
const httpReg =
/(http|https|ftp):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?/gi;
return text.replace(httpReg, '<a href="$&" target="_blank">$&</a>');
return text.replace(httpReg, ` $& `);
};
export const getErrText = (err: any, def = '') => {
return typeof err === 'string' ? err : err?.message || def;
};