feat: iframe embed

This commit is contained in:
archer
2023-07-28 17:44:07 +08:00
parent 5d0c8fa462
commit f764d81cdd
10 changed files with 152 additions and 134 deletions

View File

@@ -2,9 +2,8 @@ import { GET, POST, DELETE, PUT } from './request';
import type { ChatHistoryItemType } from '@/types/chat';
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
import { RequestPaging } from '../types/index';
import type { ShareChatSchema } from '@/types/mongoSchema';
import type { OutLinkSchema } from '@/types/mongoSchema';
import type { ShareChatEditType } from '@/types/app';
import type { QuoteItemType } from '@/types/chat';
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
/**
@@ -50,6 +49,12 @@ export const delChatRecordByIndex = (data: { chatId: string; contentId: string }
export const putChatHistory = (data: UpdateHistoryProps) =>
PUT('/chat/history/updateChatHistory', data);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init`, data);
/**
* create a shareChat
*/
@@ -63,15 +68,9 @@ export const createShareChat = (
* get shareChat
*/
export const getShareChatList = (appId: string) =>
GET<ShareChatSchema[]>(`/chat/shareChat/list`, { appId });
GET<OutLinkSchema[]>(`/chat/shareChat/list`, { appId });
/**
* delete a shareChat
*/
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
/**
* 初始化分享聊天
*/
export const initShareChatInfo = (data: { shareId: string }) =>
GET<InitShareChatResponse>(`/chat/shareChat/init`, data);

View File

@@ -1,6 +1,7 @@
export enum BillSourceEnum {
fastgpt = 'fastgpt',
api = 'api'
api = 'api',
shareLink = 'shareLink'
}
export enum PageTypeEnum {
login = 'login',
@@ -10,7 +11,8 @@ export enum PageTypeEnum {
export const BillSourceMap: Record<`${BillSourceEnum}`, string> = {
[BillSourceEnum.fastgpt]: 'FastGpt 平台',
[BillSourceEnum.api]: 'Api'
[BillSourceEnum.api]: 'Api',
[BillSourceEnum.shareLink]: '免登录链接'
};
export enum PromotionEnum {

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser, authApp, authShareChat } from '@/service/utils/auth';
import { authUser, authApp, authShareChat, AuthUserTypeEnum } from '@/service/utils/auth';
import { sseErrRes, jsonRes } from '@/service/response';
import { withNextCors } from '@/service/utils/tools';
import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat';
@@ -25,7 +25,6 @@ import { pushTaskBill } from '@/service/events/pushBill';
import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat';
import { UserModelSchema } from '@/types/mongoSchema';
import { getAIChatApi } from '@/service/ai/openai';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
type FastGptWebChatProps = {
@@ -84,7 +83,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (!user) {
throw new Error('Account is error');
}
if (authType !== 'token') {
if (authType === AuthUserTypeEnum.apikey || shareId) {
user.openaiAccount = undefined;
}
@@ -208,7 +207,11 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
appName: app.name,
appId,
userId,
source: authType === 'apikey' ? BillSourceEnum.api : BillSourceEnum.fastgpt,
source: (() => {
if (authType === 'apikey') return BillSourceEnum.api;
if (shareId) return BillSourceEnum.shareLink;
return BillSourceEnum.fastgpt;
})(),
response: responseData,
shareId
});

View File

@@ -19,11 +19,10 @@ import {
} 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 { formatTimeToChatTime, useCopyData } from '@/utils/tools';
import { useForm } from 'react-hook-form';
import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/app';
@@ -67,8 +66,8 @@ const Share = ({ appId }: { appId: string }) => {
onSuccess(id) {
onCloseCreateShareChat();
refetchShareChatList();
const url = `对话地址为:${location.origin}/chat/share?shareId=${id}`;
copyData(url, '已复制分享地址');
const url = `${location.origin}/chat/share?shareId=${id}`;
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
resetShareChat(defaultShareChat);
}
});
@@ -116,40 +115,53 @@ const Share = ({ appId }: { appId: string }) => {
<Td>{item.name}</Td>
<Td>{formatPrice(item.total)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
<Td>
<Flex>
<MyTooltip label={'复制分享链接'}>
<MyIcon
mr={3}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
copyData(url, '已复制分享链接');
}}
/>
</MyTooltip>
<MyTooltip label={'删除链接'}>
<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);
}}
/>
</MyTooltip>
</Flex>
<Td display={'flex'} alignItems={'center'}>
<MyTooltip label={'嵌入网页'}>
<MyIcon
mr={4}
name="apiLight"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
const src = `${location.origin}/js/iframe.js`;
const script = `<script src="${src}" id="fastgpt-iframe" data-src="${url}" data-color="#4e83fd"></script>`;
copyData(script, '已复制嵌入 Script可在应用 HTML 底部嵌入', 3000);
}}
/>
</MyTooltip>
<MyTooltip label={'复制分享链接'}>
<MyIcon
mr={4}
name="copy"
w={'14px'}
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
copyData(url, '已复制分享链接,可直接分享使用');
}}
/>
</MyTooltip>
<MyTooltip label={'删除链接'}>
<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);
}}
/>
</MyTooltip>
</Td>
</Tr>
))}

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, useEffect } from 'react';
import {
Box,
Grid,

View File

@@ -8,7 +8,7 @@ import { useRouter } from 'next/router';
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
const { onExportChat } = useChatBox();
const router = useRouter();
const { appId } = router.query;
const { appId, shareId } = router.query;
const menuList = useMemo(
() => [
@@ -18,7 +18,8 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
onClick: () => {
router.replace({
query: {
appId
appId,
shareId
}
});
}
@@ -35,7 +36,7 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
},
{ icon: 'pdf', label: 'PDF导出', onClick: () => onExportChat({ type: 'pdf', history }) }
],
[appId, history, onExportChat, router]
[appId, history, onExportChat, router, shareId]
);
return history.length > 0 ? (
@@ -59,7 +60,9 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
))}
</MenuList>
</Menu>
) : null;
) : (
<Box w={'28px'} h={'28px'} />
);
};
export default ToolMenu;

View File

@@ -2,11 +2,14 @@ import type { NextApiRequest } from 'next';
import jwt from 'jsonwebtoken';
import Cookie from 'cookie';
import { App, OpenApi, User, OutLink, KB } from '../mongo';
import type { AppSchema, UserModelSchema } from '@/types/mongoSchema';
import { formatPrice } from '@/utils/user';
import type { AppSchema } from '@/types/mongoSchema';
import { ERROR_ENUM } from '../errorCode';
export type AuthType = 'token' | 'root' | 'apikey';
export enum AuthUserTypeEnum {
token = 'token',
root = 'root',
apikey = 'apikey'
}
export const parseCookie = (cookie?: string): Promise<string> => {
return new Promise((resolve, reject) => {
@@ -124,28 +127,28 @@ export const authUser = async ({
let uid = '';
let appId = '';
let authType: AuthType = 'token';
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
if (authToken) {
uid = await parseCookie(cookie);
authType = 'token';
authType = AuthUserTypeEnum.token;
} else if (authRoot) {
uid = await parseRootKey(rootkey, userid);
authType = 'root';
authType = AuthUserTypeEnum.root;
} else if (cookie) {
uid = await parseCookie(cookie);
authType = 'token';
authType = AuthUserTypeEnum.token;
} else if (apikey) {
uid = await parseOpenApiKey(apikey);
authType = 'apikey';
authType = AuthUserTypeEnum.apikey;
} else if (authorization) {
const authResponse = await parseAuthorization(authorization);
uid = authResponse.uid;
appId = authResponse.appId;
authType = 'apikey';
authType = AuthUserTypeEnum.apikey;
} else if (rootkey) {
uid = await parseRootKey(rootkey, userid);
authType = 'root';
authType = AuthUserTypeEnum.root;
} else {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
@@ -229,6 +232,6 @@ export const authShareChat = async ({ shareId }: { shareId: string }) => {
user,
userId: String(shareChat.userId),
appId: String(shareChat.appId),
authType: 'token' as AuthType
authType: AuthUserTypeEnum.token
};
};

View File

@@ -4,7 +4,7 @@ import type { DataType } from './data';
import { BillSourceEnum, InformTypeEnum } from '@/constants/user';
import { TrainingModeEnum } from '@/constants/plugin';
import type { AppModuleItemType } from './app';
import { ChatSourceEnum } from '@/constants/chat';
import { ChatSourceEnum, OutLinkTypeEnum } from '@/constants/chat';
export interface UserModelSchema {
_id: string;
@@ -144,6 +144,7 @@ export interface OutLinkSchema {
name: string;
total: number;
lastTime: Date;
type: `${OutLinkTypeEnum}`;
}
export interface kbSchema {

View File

@@ -9,7 +9,7 @@ export const useCopyData = () => {
const { toast } = useToast();
return {
copyData: async (data: string, title: string = '复制成功') => {
copyData: async (data: string, title: string = '复制成功', duration = 1000) => {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(data);
@@ -28,7 +28,7 @@ export const useCopyData = () => {
toast({
title,
status: 'success',
duration: 1000
duration
});
}
};