mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
feat: share unlogin.perf: link format and model ui
This commit is contained in:
parent
a62a9c4067
commit
246ee973ec
|
|
@ -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",
|
||||
|
|
|
|||
185
pnpm-lock.yaml
185
pnpm-lock.yaml
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
### Fast GPT V3.6
|
||||
### Fast GPT V3.7
|
||||
|
||||
- 新增 - 分享免登录聊天框。可以直接为模型生成一个分享链接,其他人可以通过这个链接直接使用对话。
|
||||
- 优化 - UI 细节。
|
||||
- 新增 - 知识库与 AI 助手对多对关系,一个知识库可以被多个 AI 助手关联,一个 AI 助手可以关联多个知识库。
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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'}>
|
||||
搜索模式 
|
||||
</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'}>
|
||||
搜索模式 
|
||||
</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 />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -230,7 +230,8 @@ export const authShareChat = async ({
|
|||
// 获取 model 数据
|
||||
const { model, showModelDetail } = await authModel({
|
||||
modelId,
|
||||
userId
|
||||
userId,
|
||||
authOwner: false
|
||||
});
|
||||
|
||||
// 获取 user 的 apiKey
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/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;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue