feat: share unlogin.perf: link format and model ui
This commit is contained in:
@@ -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, {
|
||||
|
||||
46
src/pages/api/plugins/kb/detail.ts
Normal file
46
src/pages/api/plugins/kb/detail.ts
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user