mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
google login and power share link (#292)
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
"show_doc": true,
|
"show_doc": true,
|
||||||
"systemTitle": "FastGPT",
|
"systemTitle": "FastGPT",
|
||||||
"authorText": "Made by FastGPT Team.",
|
"authorText": "Made by FastGPT Team.",
|
||||||
"exportLimitMinutes": 0,
|
"limit": {
|
||||||
|
"exportLimitMinutes": 0
|
||||||
|
},
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
"SystemParams": {
|
"SystemParams": {
|
||||||
|
@@ -83,14 +83,18 @@
|
|||||||
"Delete Failed": "Delete Failed",
|
"Delete Failed": "Delete Failed",
|
||||||
"Delete Success": "Delete Successful",
|
"Delete Success": "Delete Successful",
|
||||||
"Delete Warning": "Warning",
|
"Delete Warning": "Warning",
|
||||||
|
"Edit": "Edit",
|
||||||
|
"Expired Time": "Expired",
|
||||||
"Filed is repeat": "Filed is repeated",
|
"Filed is repeat": "Filed is repeated",
|
||||||
"Filed is repeated": "",
|
"Filed is repeated": "",
|
||||||
"Input": "Input",
|
"Input": "Input",
|
||||||
|
"Name is empty": "Name is empty",
|
||||||
"Output": "Output",
|
"Output": "Output",
|
||||||
"Password inconsistency": "Password inconsistency",
|
"Password inconsistency": "Password inconsistency",
|
||||||
"Rename": "Rename",
|
"Rename": "Rename",
|
||||||
"Search": "Search",
|
"Search": "Search",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
|
"Update Successful": "Update Successful",
|
||||||
"export": ""
|
"export": ""
|
||||||
},
|
},
|
||||||
"dataset": {
|
"dataset": {
|
||||||
@@ -192,6 +196,25 @@
|
|||||||
"Store": "Store",
|
"Store": "Store",
|
||||||
"Tools": "Tools"
|
"Tools": "Tools"
|
||||||
},
|
},
|
||||||
|
"outlink": {
|
||||||
|
"Copy Iframe": "Copy Iframe",
|
||||||
|
"Copy Link": "Copy",
|
||||||
|
"Create Ifrme Window": "Create Iframe Link",
|
||||||
|
"Create Share Window": "Create Share Window",
|
||||||
|
"Delete Link": "Delete",
|
||||||
|
"Edit Ifrme Link": "Edit Iframe Link",
|
||||||
|
"Edit Link": "Edit",
|
||||||
|
"Edit Share Window": "Edit Share Window",
|
||||||
|
"Link Name": "Link Name",
|
||||||
|
"Link is empty": "",
|
||||||
|
"Max credit": "Credit",
|
||||||
|
"Max credit tips": "What is the maximum amount of money that can be consumed by the link? If the link is exceeded, it will be banned. -1 indicates no limit.",
|
||||||
|
"QPM": "QPM",
|
||||||
|
"QPM Tips": "The maximum number of queries per IP address per minute",
|
||||||
|
"QPM is empty": "QPM is empty",
|
||||||
|
"Response Detail": "Detail",
|
||||||
|
"Response Detail tips": "Whether detailed data such as references and full context need to be returned"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Account": "Account",
|
"Account": "Account",
|
||||||
"Amount of earnings": "Earnings",
|
"Amount of earnings": "Earnings",
|
||||||
|
@@ -83,14 +83,18 @@
|
|||||||
"Delete Failed": "删除失败",
|
"Delete Failed": "删除失败",
|
||||||
"Delete Success": "删除成功",
|
"Delete Success": "删除成功",
|
||||||
"Delete Warning": "删除警告",
|
"Delete Warning": "删除警告",
|
||||||
|
"Edit": "编辑",
|
||||||
|
"Expired Time": "过期时间",
|
||||||
"Filed is repeat": "",
|
"Filed is repeat": "",
|
||||||
"Filed is repeated": "字段重复了",
|
"Filed is repeated": "字段重复了",
|
||||||
"Input": "输入",
|
"Input": "输入",
|
||||||
|
"Name is empty": "名称不能为空",
|
||||||
"Output": "输出",
|
"Output": "输出",
|
||||||
"Password inconsistency": "两次密码不一致",
|
"Password inconsistency": "两次密码不一致",
|
||||||
"Rename": "重命名",
|
"Rename": "重命名",
|
||||||
"Search": "搜索",
|
"Search": "搜索",
|
||||||
"Status": "状态",
|
"Status": "状态",
|
||||||
|
"Update Successful": "更新成功",
|
||||||
"export": ""
|
"export": ""
|
||||||
},
|
},
|
||||||
"dataset": {
|
"dataset": {
|
||||||
@@ -192,6 +196,25 @@
|
|||||||
"Store": "应用市场",
|
"Store": "应用市场",
|
||||||
"Tools": "工具"
|
"Tools": "工具"
|
||||||
},
|
},
|
||||||
|
"outlink": {
|
||||||
|
"Copy Iframe": "复制嵌入",
|
||||||
|
"Copy Link": "复制",
|
||||||
|
"Create Ifrme Window": "创建嵌入链接",
|
||||||
|
"Create Share Window": "创建免登录窗口",
|
||||||
|
"Delete Link": "删除链接",
|
||||||
|
"Edit Ifrme Link": "更新嵌入链接",
|
||||||
|
"Edit Link": "编辑",
|
||||||
|
"Edit Share Window": "更新分享窗口",
|
||||||
|
"Link Name": "分享链接的名字",
|
||||||
|
"Link is empty": "",
|
||||||
|
"Max credit": "最大金额",
|
||||||
|
"Max credit tips": "该链接最大可消耗多少金额,超出后链接将被禁止使用。-1 代表无限制。",
|
||||||
|
"QPM": "",
|
||||||
|
"QPM Tips": "每个 IP 每分钟最多提问多少次",
|
||||||
|
"QPM is empty": "QPM 不能为空",
|
||||||
|
"Response Detail": "返回详情",
|
||||||
|
"Response Detail tips": "是否需要返回引用、完整上下文等详细数据"
|
||||||
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"Account": "账号",
|
"Account": "账号",
|
||||||
"Amount of earnings": "收益(¥)",
|
"Amount of earnings": "收益(¥)",
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import { GET, POST, DELETE, PUT } from './request';
|
import { GET, POST, DELETE, PUT } from './request';
|
||||||
import type { ChatHistoryItemType } from '@/types/chat';
|
import type { ChatHistoryItemType } from '@/types/chat';
|
||||||
import type { InitChatResponse, InitShareChatResponse } from './response/chat';
|
import type { InitChatResponse } from './response/chat';
|
||||||
import { RequestPaging } from '../types/index';
|
import { RequestPaging } from '../types/index';
|
||||||
import type { OutLinkSchema } from '@/types/mongoSchema';
|
|
||||||
import type { ShareChatEditType } from '@/types/app';
|
|
||||||
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
|
import type { Props as UpdateHistoryProps } from '@/pages/api/chat/history/updateChatHistory';
|
||||||
import { AdminUpdateFeedbackParams } from './request/chat';
|
import { AdminUpdateFeedbackParams } from './request/chat';
|
||||||
|
|
||||||
@@ -40,32 +38,6 @@ export const delChatRecordById = (data: { chatId: string; contentId: string }) =
|
|||||||
export const putChatHistory = (data: UpdateHistoryProps) =>
|
export const putChatHistory = (data: UpdateHistoryProps) =>
|
||||||
PUT('/chat/history/updateChatHistory', data);
|
PUT('/chat/history/updateChatHistory', data);
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化分享聊天
|
|
||||||
*/
|
|
||||||
export const initShareChatInfo = (data: { shareId: string }) =>
|
|
||||||
GET<InitShareChatResponse>(`/chat/shareChat/init`, data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create a shareChat
|
|
||||||
*/
|
|
||||||
export const createShareChat = (
|
|
||||||
data: ShareChatEditType & {
|
|
||||||
appId: string;
|
|
||||||
}
|
|
||||||
) => POST<string>(`/chat/shareChat/create`, data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get shareChat
|
|
||||||
*/
|
|
||||||
export const getShareChatList = (appId: string) =>
|
|
||||||
GET<OutLinkSchema[]>(`/chat/shareChat/list`, { appId });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* delete a shareChat
|
|
||||||
*/
|
|
||||||
export const delShareChatById = (id: string) => DELETE(`/chat/shareChat/delete?id=${id}`);
|
|
||||||
|
|
||||||
export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
|
export const userUpdateChatFeedback = (data: { chatItemId: string; userFeedback?: string }) =>
|
||||||
POST('/chat/feedback/userUpdate', data);
|
POST('/chat/feedback/userUpdate', data);
|
||||||
|
|
||||||
|
@@ -17,7 +17,12 @@ import {
|
|||||||
Response as SearchTestResponse
|
Response as SearchTestResponse
|
||||||
} from '@/pages/api/openapi/kb/searchTest';
|
} from '@/pages/api/openapi/kb/searchTest';
|
||||||
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
|
import { Props as UpdateDataProps } from '@/pages/api/openapi/kb/updateData';
|
||||||
import type { KbUpdateParams, CreateKbParams, GetKbDataListProps } from '../request/kb';
|
import type {
|
||||||
|
KbUpdateParams,
|
||||||
|
CreateKbParams,
|
||||||
|
GetKbDataListProps,
|
||||||
|
GetFileListProps
|
||||||
|
} from '../request/kb';
|
||||||
import { QuoteItemType } from '@/types/chat';
|
import { QuoteItemType } from '@/types/chat';
|
||||||
import { KbTypeEnum } from '@/constants/kb';
|
import { KbTypeEnum } from '@/constants/kb';
|
||||||
|
|
||||||
@@ -38,8 +43,8 @@ export const putKbById = (data: KbUpdateParams) => PUT(`/plugins/kb/update`, dat
|
|||||||
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
|
export const delKbById = (id: string) => DELETE(`/plugins/kb/delete?id=${id}`);
|
||||||
|
|
||||||
/* kb file */
|
/* kb file */
|
||||||
export const getKbFiles = (data: { kbId: string; searchText: string }) =>
|
export const getKbFiles = (data: GetFileListProps) =>
|
||||||
GET<KbFileItemType[]>(`/plugins/kb/file/list`, data);
|
POST<KbFileItemType[]>(`/plugins/kb/file/list`, data);
|
||||||
export const deleteKbFileById = (params: { fileId: string; kbId: string }) =>
|
export const deleteKbFileById = (params: { fileId: string; kbId: string }) =>
|
||||||
DELETE(`/plugins/kb/file/delFileByFileId`, params);
|
DELETE(`/plugins/kb/file/delFileByFileId`, params);
|
||||||
export const getFileInfoById = (fileId: string) =>
|
export const getFileInfoById = (fileId: string) =>
|
||||||
|
5
client/src/api/request/kb.d.ts
vendored
5
client/src/api/request/kb.d.ts
vendored
@@ -17,6 +17,11 @@ export type CreateKbParams = {
|
|||||||
type: `${KbTypeEnum}`;
|
type: `${KbTypeEnum}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetFileListProps = RequestPaging & {
|
||||||
|
kbId: string;
|
||||||
|
searchText: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type GetKbDataListProps = RequestPaging & {
|
export type GetKbDataListProps = RequestPaging & {
|
||||||
kbId: string;
|
kbId: string;
|
||||||
searchText: string;
|
searchText: string;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
import { baseUrl } from '../../service/lib/openai';
|
|
||||||
|
|
||||||
interface ConfigType {
|
interface ConfigType {
|
||||||
headers?: { [key: string]: string };
|
headers?: { [key: string]: string };
|
||||||
|
34
client/src/api/support/outLink.ts
Normal file
34
client/src/api/support/outLink.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { GET, POST, DELETE } from '../request';
|
||||||
|
import type { InitShareChatResponse } from '../response/chat';
|
||||||
|
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||||
|
import type { OutLinkSchema } from '@/types/support/outLink';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化分享聊天
|
||||||
|
*/
|
||||||
|
export const initShareChatInfo = (data: { shareId: string }) =>
|
||||||
|
GET<InitShareChatResponse>(`/support/outLink/init`, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a shareChat
|
||||||
|
*/
|
||||||
|
export const createShareChat = (
|
||||||
|
data: OutLinkEditType & {
|
||||||
|
appId: string;
|
||||||
|
type: OutLinkSchema['type'];
|
||||||
|
}
|
||||||
|
) => POST<string>(`/support/outLink/create`, data);
|
||||||
|
|
||||||
|
export const putShareChat = (data: OutLinkEditType) =>
|
||||||
|
POST<string>(`/support/outLink/update`, data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get shareChat
|
||||||
|
*/
|
||||||
|
export const getShareChatList = (appId: string) =>
|
||||||
|
GET<OutLinkSchema[]>(`/support/outLink/list`, { appId });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete a shareChat
|
||||||
|
*/
|
||||||
|
export const delShareChatById = (id: string) => DELETE(`/support/outLink/delete?id=${id}`);
|
@@ -5,16 +5,21 @@ import { UserAuthTypeEnum } from '@/constants/common';
|
|||||||
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
import { UserBillType, UserType, UserUpdateParams } from '@/types/user';
|
||||||
import type { PagingData, RequestPaging } from '@/types';
|
import type { PagingData, RequestPaging } from '@/types';
|
||||||
import { informSchema, PaySchema } from '@/types/mongoSchema';
|
import { informSchema, PaySchema } from '@/types/mongoSchema';
|
||||||
|
import { OAuthEnum } from '@/constants/user';
|
||||||
|
|
||||||
export const sendAuthCode = (data: {
|
export const sendAuthCode = (data: {
|
||||||
username: string;
|
username: string;
|
||||||
type: `${UserAuthTypeEnum}`;
|
type: `${UserAuthTypeEnum}`;
|
||||||
googleToken: string;
|
googleToken: string;
|
||||||
}) => POST(`/plusApi/user/account/sendCode`, data);
|
}) => POST(`/plusApi/user/inform/sendAuthCode`, data);
|
||||||
|
|
||||||
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
|
export const getTokenLogin = () => GET<UserType>('/user/account/tokenLogin');
|
||||||
export const gitLogin = (params: { code: string; inviterId?: string }) =>
|
export const oauthLogin = (params: {
|
||||||
GET<ResLogin>('/plusApi/user/account/gitLogin', params);
|
type: `${OAuthEnum}`;
|
||||||
|
code: string;
|
||||||
|
callbackUrl: string;
|
||||||
|
inviterId?: string;
|
||||||
|
}) => POST<ResLogin>('/plusApi/user/account/login/oauth', params);
|
||||||
|
|
||||||
export const postRegister = ({
|
export const postRegister = ({
|
||||||
username,
|
username,
|
||||||
@@ -27,7 +32,7 @@ export const postRegister = ({
|
|||||||
password: string;
|
password: string;
|
||||||
inviterId?: string;
|
inviterId?: string;
|
||||||
}) =>
|
}) =>
|
||||||
POST<ResLogin>(`/plusApi/user/account/register`, {
|
POST<ResLogin>(`/plusApi/user/account/register/emailAndPhone`, {
|
||||||
username,
|
username,
|
||||||
code,
|
code,
|
||||||
inviterId,
|
inviterId,
|
||||||
@@ -43,7 +48,7 @@ export const postFindPassword = ({
|
|||||||
code: string;
|
code: string;
|
||||||
password: string;
|
password: string;
|
||||||
}) =>
|
}) =>
|
||||||
POST<ResLogin>(`/plusApi/user/account/updatePasswordByCode`, {
|
POST<ResLogin>(`/plusApi/user/account/password/updateByCode`, {
|
||||||
username,
|
username,
|
||||||
code,
|
code,
|
||||||
password: createHashPassword(password)
|
password: createHashPassword(password)
|
||||||
|
1
client/src/components/Icon/icons/fill/google.svg
Normal file
1
client/src/components/Icon/icons/fill/google.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1694437679570" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7334" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M214.101333 512c0-32.512 5.546667-63.701333 15.36-92.928L57.173333 290.218667A491.861333 491.861333 0 0 0 4.693333 512c0 79.701333 18.858667 154.88 52.394667 221.610667l172.202667-129.066667A290.56 290.56 0 0 1 214.101333 512" fill="#FBBC05" p-id="7335"></path><path d="M516.693333 216.192c72.106667 0 137.258667 25.002667 188.458667 65.962667L854.101333 136.533333C763.349333 59.178667 646.997333 11.392 516.693333 11.392c-202.325333 0-376.234667 113.28-459.52 278.826667l172.373334 128.853333c39.68-118.016 152.832-202.88 287.146666-202.88" fill="#EA4335" p-id="7336"></path><path d="M516.693333 807.808c-134.357333 0-247.509333-84.864-287.232-202.88l-172.288 128.853333c83.242667 165.546667 257.152 278.826667 459.52 278.826667 124.842667 0 244.053333-43.392 333.568-124.757333l-163.584-123.818667c-46.122667 28.458667-104.234667 43.776-170.026666 43.776" fill="#34A853" p-id="7337"></path><path d="M1005.397333 512c0-29.568-4.693333-61.44-11.648-91.008H516.650667V614.4h274.602666c-13.696 65.962667-51.072 116.650667-104.533333 149.632l163.541333 123.818667c93.994667-85.418667 155.136-212.650667 155.136-375.850667" fill="#4285F4" p-id="7338"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@@ -24,6 +24,7 @@ const map = {
|
|||||||
out: require('./icons/out.svg').default,
|
out: require('./icons/out.svg').default,
|
||||||
git: require('./icons/git.svg').default,
|
git: require('./icons/git.svg').default,
|
||||||
gitFill: require('./icons/fill/git.svg').default,
|
gitFill: require('./icons/fill/git.svg').default,
|
||||||
|
googleFill: require('./icons/fill/google.svg').default,
|
||||||
menu: require('./icons/menu.svg').default,
|
menu: require('./icons/menu.svg').default,
|
||||||
edit: require('./icons/edit.svg').default,
|
edit: require('./icons/edit.svg').default,
|
||||||
inform: require('./icons/inform.svg').default,
|
inform: require('./icons/inform.svg').default,
|
||||||
|
@@ -109,7 +109,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
w={'36px'}
|
w={'36px'}
|
||||||
h={'36px'}
|
h={'36px'}
|
||||||
borderRadius={'none'}
|
borderRadius={'50%'}
|
||||||
src={userInfo?.avatar}
|
src={userInfo?.avatar}
|
||||||
fallbackSrc={HUMAN_ICON}
|
fallbackSrc={HUMAN_ICON}
|
||||||
/>
|
/>
|
||||||
@@ -157,7 +157,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
|||||||
<Link
|
<Link
|
||||||
as={NextLink}
|
as={NextLink}
|
||||||
{...itemStyles}
|
{...itemStyles}
|
||||||
href={`/account?type=inform`}
|
href={`/account?currentTab=inform`}
|
||||||
mb={0}
|
mb={0}
|
||||||
color={'#9096a5'}
|
color={'#9096a5'}
|
||||||
>
|
>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import type { ShareChatEditType } from '@/types/app';
|
|
||||||
import type { AppSchema } from '@/types/mongoSchema';
|
import type { AppSchema } from '@/types/mongoSchema';
|
||||||
|
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||||
|
|
||||||
export const defaultApp: AppSchema = {
|
export const defaultApp: AppSchema = {
|
||||||
_id: '',
|
_id: '',
|
||||||
@@ -17,6 +17,11 @@ export const defaultApp: AppSchema = {
|
|||||||
modules: []
|
modules: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export const defaultShareChat: ShareChatEditType = {
|
export const defaultOutLinkForm: OutLinkEditType = {
|
||||||
name: ''
|
name: '',
|
||||||
|
responseDetail: false,
|
||||||
|
limit: {
|
||||||
|
QPM: 100,
|
||||||
|
credit: -1
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,3 +1,7 @@
|
|||||||
|
export enum OAuthEnum {
|
||||||
|
github = 'github',
|
||||||
|
google = 'google'
|
||||||
|
}
|
||||||
export enum BillSourceEnum {
|
export enum BillSourceEnum {
|
||||||
fastgpt = 'fastgpt',
|
fastgpt = 'fastgpt',
|
||||||
api = 'api',
|
api = 'api',
|
||||||
|
@@ -124,10 +124,12 @@ const UserInfo = () => {
|
|||||||
h={['44px', '54px']}
|
h={['44px', '54px']}
|
||||||
borderRadius={'50%'}
|
borderRadius={'50%'}
|
||||||
border={theme.borders.base}
|
border={theme.borders.base}
|
||||||
|
overflow={'hidden'}
|
||||||
|
p={'2px'}
|
||||||
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
|
||||||
mb={2}
|
mb={2}
|
||||||
>
|
>
|
||||||
<Avatar src={userInfo?.avatar} w={'100%'} h={'100%'} />
|
<Avatar src={userInfo?.avatar} borderRadius={'50%'} w={'100%'} h={'100%'} />
|
||||||
</Box>
|
</Box>
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import { connectToDatabase } from '@/service/mongo';
|
import { connectToDatabase } from '@/service/mongo';
|
||||||
import { authUser, authApp, authShareChat, AuthUserTypeEnum } from '@/service/utils/auth';
|
import { authUser, authApp } from '@/service/utils/auth';
|
||||||
import { sseErrRes, jsonRes } from '@/service/response';
|
import { sseErrRes, jsonRes } from '@/service/response';
|
||||||
import { addLog, withNextCors } from '@/service/utils/tools';
|
import { addLog, withNextCors } from '@/service/utils/tools';
|
||||||
import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat';
|
import { ChatRoleEnum, ChatSourceEnum, sseResponseEventEnum } from '@/constants/chat';
|
||||||
@@ -29,6 +29,8 @@ import { ChatHistoryItemResType } from '@/types/chat';
|
|||||||
import { UserModelSchema } from '@/types/mongoSchema';
|
import { UserModelSchema } from '@/types/mongoSchema';
|
||||||
import { SystemInputEnum } from '@/constants/app';
|
import { SystemInputEnum } from '@/constants/app';
|
||||||
import { getSystemTime } from '@/utils/user';
|
import { getSystemTime } from '@/utils/user';
|
||||||
|
import { authOutLinkChat } from '@/service/support/outLink/auth';
|
||||||
|
import requestIp from 'request-ip';
|
||||||
|
|
||||||
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
||||||
type FastGptWebChatProps = {
|
type FastGptWebChatProps = {
|
||||||
@@ -82,14 +84,17 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
|
|
||||||
/* user auth */
|
/* user auth */
|
||||||
const {
|
let {
|
||||||
|
// @ts-ignore
|
||||||
|
responseDetail,
|
||||||
user,
|
user,
|
||||||
userId,
|
userId,
|
||||||
appId: authAppid,
|
appId: authAppid,
|
||||||
authType
|
authType
|
||||||
} = await (shareId
|
} = await (shareId
|
||||||
? authShareChat({
|
? authOutLinkChat({
|
||||||
shareId
|
shareId,
|
||||||
|
ip: requestIp.getClientIp(req)
|
||||||
})
|
})
|
||||||
: authUser({ req, authBalance: true }));
|
: authUser({ req, authBalance: true }));
|
||||||
|
|
||||||
@@ -112,6 +117,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const isOwner = !shareId && userId === String(app.userId);
|
const isOwner = !shareId && userId === String(app.userId);
|
||||||
|
responseDetail = isOwner || responseDetail;
|
||||||
|
|
||||||
const prompts = history.concat(gptMessage2ChatType(messages));
|
const prompts = history.concat(gptMessage2ChatType(messages));
|
||||||
if (prompts[prompts.length - 1].obj === 'AI') {
|
if (prompts[prompts.length - 1].obj === 'AI') {
|
||||||
@@ -157,7 +163,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
appId,
|
appId,
|
||||||
userId,
|
userId,
|
||||||
variables,
|
variables,
|
||||||
isOwner,
|
isOwner, // owner update use time
|
||||||
shareId,
|
shareId,
|
||||||
source: (() => {
|
source: (() => {
|
||||||
if (shareId) {
|
if (shareId) {
|
||||||
@@ -197,7 +203,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
data: '[DONE]'
|
data: '[DONE]'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isOwner && detail) {
|
if (responseDetail && detail) {
|
||||||
sseResponse({
|
sseResponse({
|
||||||
res,
|
res,
|
||||||
event: sseResponseEventEnum.appStreamResponse,
|
event: sseResponseEventEnum.appStreamResponse,
|
||||||
|
@@ -23,7 +23,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
const thirtyMinutesAgo = new Date(
|
const thirtyMinutesAgo = new Date(
|
||||||
Date.now() - (global.feConfigs?.exportLimitMinutes || 0) * 60 * 1000
|
Date.now() - (global.feConfigs?.limit?.exportLimitMinutes || 0) * 60 * 1000
|
||||||
);
|
);
|
||||||
|
|
||||||
// auth export times
|
// auth export times
|
||||||
@@ -39,7 +39,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!authTimes) {
|
if (!authTimes) {
|
||||||
const minutes = `${global.feConfigs?.exportLimitMinutes || 0} 分钟`;
|
const minutes = `${global.feConfigs?.limit?.exportLimitMinutes || 0} 分钟`;
|
||||||
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
throw new Error(`上次导出未到 ${minutes},每 ${minutes}仅可导出一次。`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
const files = await bucket
|
const files = await bucket
|
||||||
// 1 hours expired
|
// 1 hours expired
|
||||||
.find({
|
.find({
|
||||||
uploadDate: { $lte: new Date(Date.now() - 60 * 1000) },
|
uploadDate: { $lte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
|
||||||
['metadata.kbId']: kbId,
|
['metadata.kbId']: kbId,
|
||||||
['metadata.userId']: userId
|
['metadata.userId']: userId
|
||||||
})
|
})
|
||||||
|
@@ -5,14 +5,19 @@ import { authUser } from '@/service/utils/auth';
|
|||||||
import { GridFSStorage } from '@/service/lib/gridfs';
|
import { GridFSStorage } from '@/service/lib/gridfs';
|
||||||
import { PgClient } from '@/service/pg';
|
import { PgClient } from '@/service/pg';
|
||||||
import { PgTrainingTableName } from '@/constants/plugin';
|
import { PgTrainingTableName } from '@/constants/plugin';
|
||||||
import { KbFileItemType } from '@/types/plugin';
|
|
||||||
import { FileStatusEnum, OtherFileId } from '@/constants/kb';
|
import { FileStatusEnum, OtherFileId } from '@/constants/kb';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
let { kbId, searchText } = req.query as { kbId: string; searchText: string };
|
let {
|
||||||
|
pageNum = 1,
|
||||||
|
pageSize = 10,
|
||||||
|
kbId,
|
||||||
|
searchText
|
||||||
|
} = req.body as { pageNum: number; pageSize: number; kbId: string; searchText: string };
|
||||||
searchText = searchText.replace(/'/g, '');
|
searchText = searchText.replace(/'/g, '');
|
||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
@@ -21,10 +26,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
const gridFs = new GridFSStorage('dataset', userId);
|
const gridFs = new GridFSStorage('dataset', userId);
|
||||||
const bucket = gridFs.GridFSBucket();
|
const bucket = gridFs.GridFSBucket();
|
||||||
|
|
||||||
const files = await bucket
|
const mongoWhere = {
|
||||||
.find({ ['metadata.kbId']: kbId, ...(searchText && { filename: { $regex: searchText } }) })
|
['metadata.kbId']: kbId,
|
||||||
.sort({ _id: -1 })
|
...(searchText && { filename: { $regex: searchText } })
|
||||||
.toArray();
|
};
|
||||||
|
const [files, total] = await Promise.all([
|
||||||
|
bucket
|
||||||
|
.find(mongoWhere)
|
||||||
|
.sort({ _id: -1 })
|
||||||
|
.skip((pageNum - 1) * pageSize)
|
||||||
|
.limit(pageSize)
|
||||||
|
.toArray(),
|
||||||
|
mongoose.connection.db.collection('dataset.files').countDocuments(mongoWhere)
|
||||||
|
]);
|
||||||
|
|
||||||
async function GetOtherData() {
|
async function GetOtherData() {
|
||||||
return {
|
return {
|
||||||
@@ -72,8 +86,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
jsonRes<KbFileItemType[]>(res, {
|
jsonRes(res, {
|
||||||
data: data.flat().filter((item) => item.chunkLength > 0)
|
data: {
|
||||||
|
pageNum,
|
||||||
|
pageSize,
|
||||||
|
data: data.flat().filter((item) => item.chunkLength > 0),
|
||||||
|
total
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
|
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
import { jsonRes } from '@/service/response';
|
import { jsonRes } from '@/service/response';
|
||||||
import { connectToDatabase, OutLink } from '@/service/mongo';
|
import { connectToDatabase, OutLink } from '@/service/mongo';
|
||||||
import { authApp, authUser } from '@/service/utils/auth';
|
import { authApp, authUser } from '@/service/utils/auth';
|
||||||
import type { ShareChatEditType } from '@/types/app';
|
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||||
import { customAlphabet } from 'nanoid';
|
import { customAlphabet } from 'nanoid';
|
||||||
import { OutLinkTypeEnum } from '@/constants/chat';
|
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
||||||
@@ -10,8 +10,9 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);
|
|||||||
/* create a shareChat */
|
/* create a shareChat */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
const { appId, name } = req.body as ShareChatEditType & {
|
const { appId, ...props } = req.body as OutLinkEditType & {
|
||||||
appId: string;
|
appId: string;
|
||||||
|
type: `${OutLinkTypeEnum}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
@@ -28,8 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
shareId,
|
shareId,
|
||||||
userId,
|
userId,
|
||||||
appId,
|
appId,
|
||||||
name,
|
...props
|
||||||
type: OutLinkTypeEnum.share
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
@@ -6,12 +6,12 @@ import { authUser } from '@/service/utils/auth';
|
|||||||
/* delete a shareChat by shareChatId */
|
/* delete a shareChat by shareChatId */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
const { id } = req.query as {
|
const { id } = req.query as {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
await connectToDatabase();
|
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
await OutLink.findOneAndRemove({
|
await OutLink.findOneAndRemove({
|
@@ -6,7 +6,7 @@ import { authApp } from '@/service/utils/auth';
|
|||||||
import { HUMAN_ICON } from '@/constants/chat';
|
import { HUMAN_ICON } from '@/constants/chat';
|
||||||
import { getChatModelNameList, getSpecialModule } from '@/components/ChatBox/utils';
|
import { getChatModelNameList, getSpecialModule } from '@/components/ChatBox/utils';
|
||||||
|
|
||||||
/* 初始化我的聊天框,需要身份验证 */
|
/* init share chat window */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
let { shareId } = req.query as {
|
let { shareId } = req.query as {
|
@@ -7,12 +7,12 @@ import { hashPassword } from '@/service/utils/tools';
|
|||||||
/* get shareChat list by appId */
|
/* get shareChat list by appId */
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
const { appId } = req.query as {
|
const { appId } = req.query as {
|
||||||
appId: string;
|
appId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
await connectToDatabase();
|
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
const data = await OutLink.find({
|
const data = await OutLink.find({
|
||||||
@@ -22,15 +22,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
_id: -1
|
_id: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes(res, { data });
|
||||||
data: data.map((item) => ({
|
|
||||||
_id: item._id,
|
|
||||||
shareId: item.shareId,
|
|
||||||
name: item.name,
|
|
||||||
total: item.total,
|
|
||||||
lastTime: item.lastTime
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
code: 500,
|
code: 500,
|
25
client/src/pages/api/support/outLink/update.ts
Normal file
25
client/src/pages/api/support/outLink/update.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { jsonRes } from '@/service/response';
|
||||||
|
import { connectToDatabase, OutLink } from '@/service/mongo';
|
||||||
|
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||||
|
|
||||||
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
try {
|
||||||
|
await connectToDatabase();
|
||||||
|
|
||||||
|
const { _id, name, responseDetail, limit } = req.body as OutLinkEditType & {};
|
||||||
|
|
||||||
|
await OutLink.findByIdAndUpdate(_id, {
|
||||||
|
name,
|
||||||
|
responseDetail,
|
||||||
|
limit
|
||||||
|
});
|
||||||
|
|
||||||
|
jsonRes(res);
|
||||||
|
} catch (err) {
|
||||||
|
jsonRes(res, {
|
||||||
|
code: 500,
|
||||||
|
error: err
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -44,7 +44,9 @@ const defaultFeConfigs: FeConfigsType = {
|
|||||||
show_doc: true,
|
show_doc: true,
|
||||||
systemTitle: 'FastGPT',
|
systemTitle: 'FastGPT',
|
||||||
authorText: 'Made by FastGPT Team.',
|
authorText: 'Made by FastGPT Team.',
|
||||||
exportLimitMinutes: 0,
|
limit: {
|
||||||
|
exportLimitMinutes: 0
|
||||||
|
},
|
||||||
scripts: []
|
scripts: []
|
||||||
};
|
};
|
||||||
const defaultChatModels = [
|
const defaultChatModels = [
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useMemo, useRef, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
Box,
|
Box,
|
||||||
@@ -10,45 +10,46 @@ import {
|
|||||||
Th,
|
Th,
|
||||||
Td,
|
Td,
|
||||||
Tbody,
|
Tbody,
|
||||||
useDisclosure,
|
|
||||||
ModalFooter,
|
ModalFooter,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
FormControl,
|
|
||||||
Input,
|
Input,
|
||||||
useTheme
|
useTheme,
|
||||||
|
Switch,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
MenuItem
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { getShareChatList, delShareChatById, createShareChat } from '@/api/chat';
|
import {
|
||||||
|
getShareChatList,
|
||||||
|
delShareChatById,
|
||||||
|
createShareChat,
|
||||||
|
putShareChat
|
||||||
|
} from '@/api/support/outLink';
|
||||||
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
|
import { formatTimeToChatTime, useCopyData } from '@/utils/tools';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { defaultShareChat } from '@/constants/model';
|
import { defaultOutLinkForm } from '@/constants/model';
|
||||||
import type { ShareChatEditType } from '@/types/app';
|
import type { OutLinkEditType } from '@/types/support/outLink';
|
||||||
import { useRequest } from '@/hooks/useRequest';
|
import { useRequest } from '@/hooks/useRequest';
|
||||||
import { formatPrice } from '@/utils/user';
|
import { formatPrice } from '@/utils/user';
|
||||||
|
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useToast } from '@/hooks/useToast';
|
||||||
import MyTooltip from '@/components/MyTooltip';
|
import MyTooltip from '@/components/MyTooltip';
|
||||||
import MyModal from '@/components/MyModal';
|
import MyModal from '@/components/MyModal';
|
||||||
import MyRadio from '@/components/Radio';
|
import MyRadio from '@/components/Radio';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const Share = ({ appId }: { appId: string }) => {
|
const Share = ({ appId }: { appId: string }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { Loading, setIsLoading } = useLoading();
|
const { Loading, setIsLoading } = useLoading();
|
||||||
const { copyData } = useCopyData();
|
const { copyData } = useCopyData();
|
||||||
const {
|
const [editLinkData, setEditLinkData] = useState<OutLinkEditType>();
|
||||||
isOpen: isOpenCreateShareChat,
|
const { toast } = useToast();
|
||||||
onOpen: onOpenCreateShareChat,
|
|
||||||
onClose: onCloseCreateShareChat
|
|
||||||
} = useDisclosure();
|
|
||||||
const {
|
|
||||||
register: registerShareChat,
|
|
||||||
getValues: getShareChatValues,
|
|
||||||
setValue: setShareChatValues,
|
|
||||||
handleSubmit: submitShareChat,
|
|
||||||
reset: resetShareChat
|
|
||||||
} = useForm({
|
|
||||||
defaultValues: defaultShareChat
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
@@ -56,22 +57,6 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
refetch: refetchShareChatList
|
refetch: refetchShareChatList
|
||||||
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
||||||
|
|
||||||
const { mutate: onclickCreateShareChat, isLoading: creating } = useRequest({
|
|
||||||
mutationFn: async (e: ShareChatEditType) =>
|
|
||||||
createShareChat({
|
|
||||||
...e,
|
|
||||||
appId
|
|
||||||
}),
|
|
||||||
errorToast: '创建分享链接异常',
|
|
||||||
onSuccess(id) {
|
|
||||||
onCloseCreateShareChat();
|
|
||||||
refetchShareChatList();
|
|
||||||
const url = `${location.origin}/chat/share?shareId=${id}`;
|
|
||||||
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
|
|
||||||
resetShareChat(defaultShareChat);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position={'relative'} pt={[3, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
<Box position={'relative'} pt={[3, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
@@ -79,7 +64,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
免登录窗口
|
免登录窗口
|
||||||
<MyTooltip
|
<MyTooltip
|
||||||
forceShow
|
forceShow
|
||||||
label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的tokens。请保管好链接和密码。"
|
label="可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!"
|
||||||
>
|
>
|
||||||
<QuestionOutlineIcon ml={1} />
|
<QuestionOutlineIcon ml={1} />
|
||||||
</MyTooltip>
|
</MyTooltip>
|
||||||
@@ -94,7 +79,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
title: '最多创建10组'
|
title: '最多创建10组'
|
||||||
}
|
}
|
||||||
: {})}
|
: {})}
|
||||||
onClick={onOpenCreateShareChat}
|
onClick={() => setEditLinkData(defaultOutLinkForm)}
|
||||||
>
|
>
|
||||||
创建新链接
|
创建新链接
|
||||||
</Button>
|
</Button>
|
||||||
@@ -105,8 +90,14 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Tr>
|
<Tr>
|
||||||
<Th>名称</Th>
|
<Th>名称</Th>
|
||||||
<Th>金额消耗</Th>
|
<Th>金额消耗</Th>
|
||||||
|
<>
|
||||||
|
<Th>金额限制</Th>
|
||||||
|
<Th>IP限流(人/分钟)</Th>
|
||||||
|
</>
|
||||||
|
<Th>返回详情</Th>
|
||||||
|
<Th>过期时间</Th>
|
||||||
<Th>最后使用时间</Th>
|
<Th>最后使用时间</Th>
|
||||||
<Th>操作</Th>
|
<Th></Th>
|
||||||
</Tr>
|
</Tr>
|
||||||
</Thead>
|
</Thead>
|
||||||
<Tbody>
|
<Tbody>
|
||||||
@@ -114,60 +105,90 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Tr key={item._id}>
|
<Tr key={item._id}>
|
||||||
<Td>{item.name}</Td>
|
<Td>{item.name}</Td>
|
||||||
<Td>{formatPrice(item.total)}元</Td>
|
<Td>{formatPrice(item.total)}元</Td>
|
||||||
|
{item.limit && (
|
||||||
|
<>
|
||||||
|
<Td>{item.limit?.credit > -1 ? `${item.limit?.credit}元` : '无限制'}</Td>
|
||||||
|
<Td>{item.limit?.QPM}</Td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Td>{item.responseDetail ? '✔' : '✖'}</Td>
|
||||||
|
<Td>
|
||||||
|
{item.limit?.expiredTime
|
||||||
|
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||||
|
: '-'}
|
||||||
|
</Td>
|
||||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||||
<Td display={'flex'} alignItems={'center'}>
|
<Td display={'flex'} alignItems={'center'}>
|
||||||
<MyTooltip label={'嵌入网页'}>
|
<Menu autoSelect={false} isLazy>
|
||||||
<MyIcon
|
<MenuButton
|
||||||
mr={4}
|
_hover={{ bg: 'myWhite.600 ' }}
|
||||||
name="apiLight"
|
|
||||||
w={'14px'}
|
|
||||||
cursor={'pointer'}
|
cursor={'pointer'}
|
||||||
_hover={{ color: 'myBlue.600' }}
|
borderRadius={'md'}
|
||||||
onClick={() => {
|
>
|
||||||
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||||
const src = `${location.origin}/js/iframe.js`;
|
</MenuButton>
|
||||||
const script = `<script src="${src}" id="fastgpt-iframe" data-src="${url}" data-color="#4e83fd"></script>`;
|
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||||
copyData(script, '已复制嵌入 Script,可在应用 HTML 底部嵌入', 3000);
|
<MenuItem
|
||||||
}}
|
onClick={() =>
|
||||||
/>
|
setEditLinkData({
|
||||||
</MyTooltip>
|
_id: item._id,
|
||||||
<MyTooltip label={'复制分享链接'}>
|
name: item.name,
|
||||||
<MyIcon
|
responseDetail: item.responseDetail,
|
||||||
mr={4}
|
limit: item.limit
|
||||||
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);
|
py={[2, 3]}
|
||||||
}}
|
>
|
||||||
/>
|
<MyIcon name={'edit'} w={['14px', '16px']} />
|
||||||
</MyTooltip>
|
<Box ml={[1, 2]}>{t('common.Edit')}</Box>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
|
||||||
|
copyData(url, '已复制分享链接,可直接分享使用');
|
||||||
|
}}
|
||||||
|
py={[2, 3]}
|
||||||
|
>
|
||||||
|
<MyIcon name={'copy'} w={['14px', '16px']} />
|
||||||
|
<Box ml={[1, 2]}>{t('common.Copy')}</Box>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
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);
|
||||||
|
}}
|
||||||
|
py={[2, 3]}
|
||||||
|
>
|
||||||
|
<MyIcon name={'apiLight'} w={['14px', '16px']} />
|
||||||
|
<Box ml={[1, 2]}>{t('outlink.Copy Iframe')}</Box>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
onClick={async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
await delShareChatById(item._id);
|
||||||
|
refetchShareChatList();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
}}
|
||||||
|
py={[2, 3]}
|
||||||
|
>
|
||||||
|
<MyIcon name={'delete'} w={['14px', '16px']} />
|
||||||
|
<Box ml={[1, 2]}>{t('common.Delete')}</Box>
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
))}
|
))}
|
||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
|
||||||
{shareChatList.length === 0 && !isFetching && (
|
{shareChatList.length === 0 && !isFetching && (
|
||||||
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
|
<Flex h={'100%'} flexDirection={'column'} alignItems={'center'} pt={'10vh'}>
|
||||||
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
<MyIcon name="empty" w={'48px'} h={'48px'} color={'transparent'} />
|
||||||
@@ -176,56 +197,185 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
{/* create shareChat modal */}
|
{!!editLinkData && (
|
||||||
<MyModal
|
<EditLinkModal
|
||||||
isOpen={isOpenCreateShareChat}
|
appId={appId}
|
||||||
onClose={onCloseCreateShareChat}
|
type={'share'}
|
||||||
title={'创建免登录窗口'}
|
defaultData={editLinkData}
|
||||||
>
|
onCreate={(id) => {
|
||||||
<ModalBody>
|
const url = `${location.origin}/chat/share?shareId=${id}`;
|
||||||
<FormControl>
|
copyData(url, '创建成功。已复制分享地址,可直接分享使用');
|
||||||
<Flex alignItems={'center'}>
|
refetchShareChatList();
|
||||||
<Box flex={'0 0 60px'} w={0}>
|
setEditLinkData(undefined);
|
||||||
名称:
|
}}
|
||||||
</Box>
|
onEdit={() => {
|
||||||
<Input
|
toast({
|
||||||
placeholder="记录名字,仅用于展示"
|
status: 'success',
|
||||||
maxLength={20}
|
title: t('common.Update Successful')
|
||||||
{...registerShareChat('name', {
|
});
|
||||||
required: '记录名称不能为空'
|
refetchShareChatList();
|
||||||
})}
|
setEditLinkData(undefined);
|
||||||
/>
|
}}
|
||||||
</Flex>
|
onClose={() => setEditLinkData(undefined)}
|
||||||
</FormControl>
|
/>
|
||||||
</ModalBody>
|
)}
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button variant={'base'} mr={3} onClick={onCloseCreateShareChat}>
|
|
||||||
取消
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
isLoading={creating}
|
|
||||||
onClick={submitShareChat((data) => onclickCreateShareChat(data))}
|
|
||||||
>
|
|
||||||
确认
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</MyModal>
|
|
||||||
<Loading loading={isFetching} fixed={false} />
|
<Loading loading={isFetching} fixed={false} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum LinkTypeEnum {
|
// edit link modal
|
||||||
share = 'share',
|
export function EditLinkModal({
|
||||||
iframe = 'iframe'
|
appId,
|
||||||
|
type,
|
||||||
|
defaultData,
|
||||||
|
onClose,
|
||||||
|
onCreate,
|
||||||
|
onEdit
|
||||||
|
}: {
|
||||||
|
appId: string;
|
||||||
|
type: `${OutLinkTypeEnum}`;
|
||||||
|
defaultData: OutLinkEditType;
|
||||||
|
onClose: () => void;
|
||||||
|
onCreate: (id: string) => void;
|
||||||
|
onEdit: () => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const isEdit = useMemo(() => !!defaultData._id, [defaultData]);
|
||||||
|
const titleMap = useRef({
|
||||||
|
create: {
|
||||||
|
[OutLinkTypeEnum.share]: t('outlink.Create Share Window'),
|
||||||
|
[OutLinkTypeEnum.iframe]: t('outlink.Create Ifrme Window')
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
[OutLinkTypeEnum.share]: t('outlink.Edit Share Window'),
|
||||||
|
[OutLinkTypeEnum.iframe]: t('outlink.Edit Ifrme Link')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
setValue,
|
||||||
|
handleSubmit: submitShareChat
|
||||||
|
} = useForm({
|
||||||
|
defaultValues: defaultData
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutate: onclickCreate, isLoading: creating } = useRequest({
|
||||||
|
mutationFn: async (e: OutLinkEditType) =>
|
||||||
|
createShareChat({
|
||||||
|
...e,
|
||||||
|
appId,
|
||||||
|
type
|
||||||
|
}),
|
||||||
|
errorToast: '创建链接异常',
|
||||||
|
onSuccess: onCreate
|
||||||
|
});
|
||||||
|
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||||
|
mutationFn: (e: OutLinkEditType) => {
|
||||||
|
console.log(e);
|
||||||
|
return putShareChat(e);
|
||||||
|
},
|
||||||
|
errorToast: '更新链接异常',
|
||||||
|
onSuccess: onEdit
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MyModal
|
||||||
|
isOpen={true}
|
||||||
|
onClose={() => {}}
|
||||||
|
title={isEdit ? titleMap.current.edit[type] : titleMap.current.create[type]}
|
||||||
|
>
|
||||||
|
<ModalBody>
|
||||||
|
<Flex alignItems={'center'}>
|
||||||
|
<Box flex={'0 0 90px'}>{t('Name')}:</Box>
|
||||||
|
<Input
|
||||||
|
placeholder={t('outlink.Link Name') || 'Link Name'}
|
||||||
|
maxLength={20}
|
||||||
|
{...register('name', {
|
||||||
|
required: t('common.Name is empty') || 'Name is empty'
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={4}>
|
||||||
|
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||||
|
QPM:
|
||||||
|
<MyTooltip label={t('outlink.QPM Tips' || '')}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Input
|
||||||
|
max={1000}
|
||||||
|
{...register('limit.QPM', {
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
valueAsNumber: true,
|
||||||
|
required: t('outlink.QPM is empty') || ''
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={4}>
|
||||||
|
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||||
|
{t('outlink.Max credit')}:
|
||||||
|
<MyTooltip label={t('outlink.Max credit tips' || '')}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Input
|
||||||
|
{...register('limit.credit', {
|
||||||
|
min: -1,
|
||||||
|
max: 1000,
|
||||||
|
valueAsNumber: true,
|
||||||
|
required: true
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={4}>
|
||||||
|
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||||
|
{t('common.Expired Time')}:
|
||||||
|
</Flex>
|
||||||
|
<Input
|
||||||
|
type="datetime-local"
|
||||||
|
defaultValue={
|
||||||
|
defaultData.limit?.expiredTime
|
||||||
|
? dayjs(defaultData.limit?.expiredTime).format('YYYY-MM-DDTHH:mm')
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue('limit.expiredTime', new Date(e.target.value));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={4}>
|
||||||
|
<Flex flex={'0 0 90px'} alignItems={'center'}>
|
||||||
|
{t('outlink.Response Detail')}:
|
||||||
|
<MyTooltip label={t('outlink.Response Detail tips' || '')}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Switch {...register('responseDetail')} size={'lg'} />
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
isLoading={creating || updating}
|
||||||
|
onClick={submitShareChat((data) => (isEdit ? onclickUpdate(data) : onclickCreate(data)))}
|
||||||
|
>
|
||||||
|
确认
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</MyModal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const OutLink = ({ appId }: { appId: string }) => {
|
const OutLink = ({ appId }: { appId: string }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [linkType, setLinkType] = useState<`${LinkTypeEnum}`>(LinkTypeEnum.share);
|
const [linkType, setLinkType] = useState<`${OutLinkTypeEnum}`>(OutLinkTypeEnum.share);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box pt={[1, 5]}>
|
<Box pt={[1, 5]}>
|
||||||
@@ -241,21 +391,21 @@ const OutLink = ({ appId }: { appId: string }) => {
|
|||||||
icon: 'outlink_share',
|
icon: 'outlink_share',
|
||||||
title: '免登录窗口',
|
title: '免登录窗口',
|
||||||
desc: '分享链接给其他用户,无需登录即可直接进行使用',
|
desc: '分享链接给其他用户,无需登录即可直接进行使用',
|
||||||
value: LinkTypeEnum.share
|
value: OutLinkTypeEnum.share
|
||||||
}
|
}
|
||||||
// {
|
// {
|
||||||
// icon: 'outlink_iframe',
|
// icon: 'outlink_iframe',
|
||||||
// title: '网页嵌入',
|
// title: '网页嵌入',
|
||||||
// desc: '嵌入到已有网页中,右下角会生成对话按键',
|
// desc: '嵌入到已有网页中,右下角会生成对话按键',
|
||||||
// value: LinkTypeEnum.iframe
|
// value: OutLinkTypeEnum.iframe
|
||||||
// }
|
// }
|
||||||
]}
|
]}
|
||||||
value={linkType}
|
value={linkType}
|
||||||
onChange={(e) => setLinkType(e as `${LinkTypeEnum}`)}
|
onChange={(e) => setLinkType(e as `${OutLinkTypeEnum}`)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{linkType === LinkTypeEnum.share && <Share appId={appId} />}
|
{linkType === OutLinkTypeEnum.share && <Share appId={appId} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo, useRef } from 'react';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { initShareChatInfo } from '@/api/chat';
|
import { initShareChatInfo } from '@/api/support/outLink';
|
||||||
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
import { Box, Flex, useDisclosure, Drawer, DrawerOverlay, DrawerContent } from '@chakra-ui/react';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
@@ -65,22 +65,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
|
|
||||||
const [editInputData, setEditInputData] = useState<InputDataType>();
|
const [editInputData, setEditInputData] = useState<InputDataType>();
|
||||||
|
|
||||||
const { data: { qaListLen = 0, vectorListLen = 0 } = {}, refetch: refetchTrainingData } =
|
|
||||||
useQuery(['getModelSplitDataList', kbId], () => getTrainingData({ kbId, init: false }), {
|
|
||||||
onError(err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const refetchData = useCallback(
|
|
||||||
(num = pageNum) => {
|
|
||||||
getData(num);
|
|
||||||
refetchTrainingData();
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[getData, pageNum, refetchTrainingData]
|
|
||||||
);
|
|
||||||
|
|
||||||
// get first page data
|
// get first page data
|
||||||
const getFirstData = useCallback(
|
const getFirstData = useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
@@ -90,12 +74,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
// interval get data
|
|
||||||
useQuery(['refetchData'], () => refetchData(1), {
|
|
||||||
refetchInterval: 5000,
|
|
||||||
enabled: qaListLen > 0 || vectorListLen > 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// get file info
|
// get file info
|
||||||
const { data: fileInfo } = useQuery(['getFileInfo', fileId], () => getFileInfoById(fileId));
|
const { data: fileInfo } = useQuery(['getFileInfo', fileId], () => getFileInfoById(fileId));
|
||||||
const fileIcon = useMemo(
|
const fileIcon = useMemo(
|
||||||
@@ -122,7 +100,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
filename
|
filename
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
successToast: `导出成功,下次导出需要 ${feConfigs.exportLimitMinutes} 分钟后`,
|
successToast: `导出成功,下次导出需要 ${feConfigs?.limit?.exportLimitMinutes} 分钟后`,
|
||||||
errorToast: '导出异常'
|
errorToast: '导出异常'
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -136,7 +114,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
fontSize={['sm', 'md']}
|
fontSize={['sm', 'md']}
|
||||||
alignItems={'center'}
|
alignItems={'center'}
|
||||||
>
|
>
|
||||||
<Image src={fileIcon} w={'16px'} mr={2} alt={''} />
|
<Image src={fileIcon || '/imgs/files/file.svg'} w={'16px'} mr={2} alt={''} />
|
||||||
{t(fileInfo?.filename || 'Filename')}
|
{t(fileInfo?.filename || 'Filename')}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Button
|
<Button
|
||||||
@@ -172,15 +150,6 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
<Box as={'span'} fontSize={['md', 'lg']}>
|
<Box as={'span'} fontSize={['md', 'lg']}>
|
||||||
{total}组
|
{total}组
|
||||||
</Box>
|
</Box>
|
||||||
<Box as={'span'}>
|
|
||||||
{(qaListLen > 0 || vectorListLen > 0) && (
|
|
||||||
<>
|
|
||||||
({qaListLen > 0 ? `${qaListLen}条数据正在拆分,` : ''}
|
|
||||||
{vectorListLen > 0 ? `${vectorListLen}条数据正在生成索引,` : ''}
|
|
||||||
请耐心等待... )
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box flex={1} mr={1} />
|
<Box flex={1} mr={1} />
|
||||||
<MyInput
|
<MyInput
|
||||||
@@ -262,7 +231,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
try {
|
try {
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
await delOneKbDataByDataId(item.id);
|
await delOneKbDataByDataId(item.id);
|
||||||
refetchData(pageNum);
|
getData(pageNum);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: getErrText(error),
|
title: getErrText(error),
|
||||||
@@ -297,7 +266,7 @@ const DataCard = ({ kbId }: { kbId: string }) => {
|
|||||||
kbId={kbId}
|
kbId={kbId}
|
||||||
defaultValues={editInputData}
|
defaultValues={editInputData}
|
||||||
onClose={() => setEditInputData(undefined)}
|
onClose={() => setEditInputData(undefined)}
|
||||||
onSuccess={() => refetchData()}
|
onSuccess={() => getData(pageNum)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ConfirmModal />
|
<ConfirmModal />
|
||||||
|
@@ -11,9 +11,8 @@ import {
|
|||||||
Tbody,
|
Tbody,
|
||||||
Image
|
Image
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { getKbFiles, deleteKbFileById } from '@/api/plugins/kb';
|
import { getKbFiles, deleteKbFileById, getTrainingData } from '@/api/plugins/kb';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useToast } from '@/hooks/useToast';
|
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { formatFileSize } from '@/utils/tools';
|
import { formatFileSize } from '@/utils/tools';
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
@@ -26,30 +25,47 @@ import { useRequest } from '@/hooks/useRequest';
|
|||||||
import { useLoading } from '@/hooks/useLoading';
|
import { useLoading } from '@/hooks/useLoading';
|
||||||
import { FileStatusEnum } from '@/constants/kb';
|
import { FileStatusEnum } from '@/constants/kb';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
|
import { usePagination } from '@/hooks/usePagination';
|
||||||
|
import { KbFileItemType } from '@/types/plugin';
|
||||||
|
import { useGlobalStore } from '@/store/global';
|
||||||
|
|
||||||
const FileCard = ({ kbId }: { kbId: string }) => {
|
const FileCard = ({ kbId }: { kbId: string }) => {
|
||||||
const BoxRef = useRef<HTMLDivElement>(null);
|
const BoxRef = useRef<HTMLDivElement>(null);
|
||||||
const lastSearch = useRef('');
|
const lastSearch = useRef('');
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchText, setSearchText] = useState('');
|
|
||||||
const { Loading } = useLoading();
|
const { Loading } = useLoading();
|
||||||
|
const [searchText, setSearchText] = useState('');
|
||||||
|
const { setLoading } = useGlobalStore();
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
content: t('kb.Confirm to delete the file')
|
content: t('kb.Confirm to delete the file')
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: files = [],
|
data: files,
|
||||||
refetch,
|
Pagination,
|
||||||
isInitialLoading
|
total,
|
||||||
} = useQuery(['getFiles', kbId], () => getKbFiles({ kbId, searchText }), {
|
isLoading,
|
||||||
refetchInterval: 6000,
|
getData,
|
||||||
refetchOnWindowFocus: true
|
pageNum,
|
||||||
|
pageSize
|
||||||
|
} = usePagination<KbFileItemType>({
|
||||||
|
api: getKbFiles,
|
||||||
|
pageSize: 40,
|
||||||
|
params: {
|
||||||
|
kbId,
|
||||||
|
searchText
|
||||||
|
},
|
||||||
|
onChange() {
|
||||||
|
if (BoxRef.current) {
|
||||||
|
BoxRef.current.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const debounceRefetch = useCallback(
|
const debounceRefetch = useCallback(
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
refetch();
|
getData(1);
|
||||||
lastSearch.current = searchText;
|
lastSearch.current = searchText;
|
||||||
}, 300),
|
}, 300),
|
||||||
[]
|
[]
|
||||||
@@ -68,14 +84,19 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
|||||||
[files]
|
[files]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { mutate: onDeleteFile, isLoading } = useRequest({
|
const { mutate: onDeleteFile } = useRequest({
|
||||||
mutationFn: (fileId: string) =>
|
mutationFn: (fileId: string) => {
|
||||||
deleteKbFileById({
|
setLoading(true);
|
||||||
|
return deleteKbFileById({
|
||||||
fileId,
|
fileId,
|
||||||
kbId
|
kbId
|
||||||
}),
|
});
|
||||||
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
refetch();
|
getData(pageNum);
|
||||||
|
},
|
||||||
|
onSettled() {
|
||||||
|
setLoading(false);
|
||||||
},
|
},
|
||||||
successToast: t('common.Delete Success'),
|
successToast: t('common.Delete Success'),
|
||||||
errorToast: t('common.Delete Failed')
|
errorToast: t('common.Delete Failed')
|
||||||
@@ -92,12 +113,37 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// training data
|
||||||
|
const { data: { qaListLen = 0, vectorListLen = 0 } = {}, refetch: refetchTrainingData } =
|
||||||
|
useQuery(['getModelSplitDataList', kbId], () => getTrainingData({ kbId, init: false }), {
|
||||||
|
onError(err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useQuery(['refetchTrainingData'], refetchTrainingData, {
|
||||||
|
refetchInterval: 8000,
|
||||||
|
enabled: qaListLen > 0 || vectorListLen > 0
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={BoxRef} position={'relative'} py={[1, 5]} h={'100%'} overflow={'overlay'}>
|
<Box ref={BoxRef} position={'relative'} py={[1, 5]} h={'100%'} overflow={'overlay'}>
|
||||||
<Flex justifyContent={'space-between'} px={5}>
|
<Flex justifyContent={'space-between'} px={5}>
|
||||||
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
|
<Box>
|
||||||
{t('kb.Files', { total: files.length })}
|
<Box fontWeight={'bold'} fontSize={'lg'} mr={2}>
|
||||||
|
{t('kb.Files', { total: files.length })}
|
||||||
|
</Box>
|
||||||
|
<Box as={'span'} fontSize={'sm'}>
|
||||||
|
{(qaListLen > 0 || vectorListLen > 0) && (
|
||||||
|
<>
|
||||||
|
({qaListLen > 0 ? `${qaListLen}条数据正在拆分,` : ''}
|
||||||
|
{vectorListLen > 0 ? `${vectorListLen}条数据正在生成索引,` : ''}
|
||||||
|
请耐心等待... )
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Flex alignItems={'center'}>
|
<Flex alignItems={'center'}>
|
||||||
<MyInput
|
<MyInput
|
||||||
leftIcon={
|
leftIcon={
|
||||||
@@ -112,12 +158,12 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
|||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (searchText === lastSearch.current) return;
|
if (searchText === lastSearch.current) return;
|
||||||
refetch();
|
getData(1);
|
||||||
}}
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (searchText === lastSearch.current) return;
|
if (searchText === lastSearch.current) return;
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
refetch();
|
getData(1);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -198,9 +244,14 @@ const FileCard = ({ kbId }: { kbId: string }) => {
|
|||||||
</Tbody>
|
</Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
|
{total > pageSize && (
|
||||||
|
<Flex mt={2} justifyContent={'center'}>
|
||||||
|
<Pagination />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
<ConfirmModal />
|
<ConfirmModal />
|
||||||
<Loading loading={isInitialLoading || isLoading} />
|
<Loading loading={isLoading} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -40,7 +40,11 @@ const QAImport = ({ kbId }: { kbId: string }) => {
|
|||||||
|
|
||||||
// price count
|
// price count
|
||||||
const price = useMemo(() => {
|
const price = useMemo(() => {
|
||||||
return formatPrice(files.reduce((sum, file) => sum + file.tokens, 0) * unitPrice * 1.3);
|
const filesToken = files.reduce((sum, file) => sum + file.tokens, 0);
|
||||||
|
const promptTokens = files.reduce((sum, file) => sum + file.chunks.length, 0) * 139;
|
||||||
|
const totalToken = (filesToken + promptTokens) * 1.8;
|
||||||
|
|
||||||
|
return formatPrice(totalToken * unitPrice);
|
||||||
}, [files, unitPrice]);
|
}, [files, unitPrice]);
|
||||||
|
|
||||||
const { openConfirm, ConfirmModal } = useConfirm({
|
const { openConfirm, ConfirmModal } = useConfirm({
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
import React, { useState, Dispatch, useCallback } from 'react';
|
import React, { useState, Dispatch, useCallback, useRef } from 'react';
|
||||||
import { FormControl, Flex, Input, Button, FormErrorMessage, Box } from '@chakra-ui/react';
|
import { FormControl, Flex, Input, Button, FormErrorMessage, Box } from '@chakra-ui/react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { PageTypeEnum } from '@/constants/user';
|
import { OAuthEnum, PageTypeEnum } from '@/constants/user';
|
||||||
import { postLogin } from '@/api/user';
|
import { postLogin } from '@/api/user';
|
||||||
import type { ResLogin } from '@/api/response/user';
|
import type { ResLogin } from '@/api/response/user';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { feConfigs } from '@/store/static';
|
import { feConfigs } from '@/store/static';
|
||||||
import { useGlobalStore } from '@/store/global';
|
import { useGlobalStore } from '@/store/global';
|
||||||
import MyIcon from '@/components/Icon';
|
import MyIcon from '@/components/Icon';
|
||||||
|
import { customAlphabet } from 'nanoid';
|
||||||
|
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
setPageType: Dispatch<`${PageTypeEnum}`>;
|
setPageType: Dispatch<`${PageTypeEnum}`>;
|
||||||
@@ -58,18 +60,29 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
|||||||
[loginSuccess, toast]
|
[loginSuccess, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onclickGit = useCallback(() => {
|
const redirectUri = `${location.origin}/login/provider`;
|
||||||
setLoginStore({
|
const state = useRef(nanoid());
|
||||||
provider: 'git',
|
|
||||||
lastRoute
|
const oAuthList = [
|
||||||
});
|
...(feConfigs?.oauth?.github
|
||||||
router.replace(
|
? [
|
||||||
`https://github.com/login/oauth/authorize?client_id=${
|
{
|
||||||
feConfigs?.gitLoginKey
|
provider: OAuthEnum.github,
|
||||||
}&redirect_uri=${`${location.origin}/login/provider`}&scope=user:email%20read:user`,
|
icon: 'gitFill',
|
||||||
'_self'
|
redirectUrl: `https://github.com/login/oauth/authorize?client_id=${feConfigs?.oauth?.github}&redirect_uri=${redirectUri}&state=${state.current}&scope=user:email%20read:user`
|
||||||
);
|
}
|
||||||
}, [lastRoute, setLoginStore]);
|
]
|
||||||
|
: []),
|
||||||
|
...(feConfigs?.oauth?.google
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
provider: OAuthEnum.google,
|
||||||
|
icon: 'googleFill',
|
||||||
|
redirectUrl: `https://accounts.google.com/o/oauth2/v2/auth?client_id=${feConfigs?.oauth?.google}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20openid&include_granted_scopes=true`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -138,14 +151,24 @@ const LoginForm = ({ setPageType, loginSuccess }: Props) => {
|
|||||||
</Button>
|
</Button>
|
||||||
{feConfigs?.show_register && (
|
{feConfigs?.show_register && (
|
||||||
<>
|
<>
|
||||||
<Flex mt={10} justifyContent={'center'} alignItems={'center'}>
|
<Flex mt={10} justifyContent={'space-around'} alignItems={'center'}>
|
||||||
<MyIcon
|
{oAuthList.map((item) => (
|
||||||
name="gitFill"
|
<MyIcon
|
||||||
w={'34px'}
|
key={item.provider}
|
||||||
cursor={'pointer'}
|
name={item.icon as any}
|
||||||
color={'myGray.800'}
|
w={'34px'}
|
||||||
onClick={onclickGit}
|
cursor={'pointer'}
|
||||||
/>
|
color={'myGray.800'}
|
||||||
|
onClick={() => {
|
||||||
|
setLoginStore({
|
||||||
|
provider: item.provider,
|
||||||
|
lastRoute,
|
||||||
|
state: state.current
|
||||||
|
});
|
||||||
|
router.replace(item.redirectUrl, '_self');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</Flex>
|
</Flex>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@@ -5,14 +5,14 @@ import { ResLogin } from '@/api/response/user';
|
|||||||
import { useChatStore } from '@/store/chat';
|
import { useChatStore } from '@/store/chat';
|
||||||
import { useUserStore } from '@/store/user';
|
import { useUserStore } from '@/store/user';
|
||||||
import { setToken } from '@/utils/user';
|
import { setToken } from '@/utils/user';
|
||||||
import { gitLogin } from '@/api/user';
|
import { oauthLogin } from '@/api/user';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import Loading from '@/components/Loading';
|
import Loading from '@/components/Loading';
|
||||||
import { serviceSideProps } from '@/utils/i18n';
|
import { serviceSideProps } from '@/utils/i18n';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { getErrText } from '@/utils/tools';
|
import { getErrText } from '@/utils/tools';
|
||||||
|
|
||||||
const provider = ({ code }: { code: string }) => {
|
const provider = ({ code, state }: { code: string; state: string }) => {
|
||||||
const { loginStore } = useGlobalStore();
|
const { loginStore } = useGlobalStore();
|
||||||
const { setLastChatId, setLastChatAppId } = useChatStore();
|
const { setLastChatId, setLastChatAppId } = useChatStore();
|
||||||
const { setUserInfo } = useUserStore();
|
const { setUserInfo } = useUserStore();
|
||||||
@@ -36,45 +36,55 @@ const provider = ({ code }: { code: string }) => {
|
|||||||
[setLastChatId, setLastChatAppId, setUserInfo, router, loginStore?.lastRoute]
|
[setLastChatId, setLastChatAppId, setUserInfo, router, loginStore?.lastRoute]
|
||||||
);
|
);
|
||||||
|
|
||||||
const authCode = useCallback(async () => {
|
const authCode = useCallback(
|
||||||
if (!code) return;
|
async (code: string) => {
|
||||||
if (!loginStore) {
|
if (!loginStore) {
|
||||||
router.replace('/login');
|
router.replace('/login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await (async () => {
|
const res = await oauthLogin({
|
||||||
if (loginStore.provider === 'git') {
|
type: loginStore?.provider,
|
||||||
return gitLogin({
|
code,
|
||||||
code,
|
callbackUrl: `${location.origin}/login/provider`,
|
||||||
inviterId: localStorage.getItem('inviterId') || undefined
|
inviterId: localStorage.getItem('inviterId') || undefined
|
||||||
|
});
|
||||||
|
if (!res) {
|
||||||
|
toast({
|
||||||
|
status: 'warning',
|
||||||
|
title: '登录异常'
|
||||||
});
|
});
|
||||||
|
return setTimeout(() => {
|
||||||
|
router.replace('/login');
|
||||||
|
}, 1000);
|
||||||
}
|
}
|
||||||
return null;
|
loginSuccess(res);
|
||||||
})();
|
} catch (error) {
|
||||||
if (!res) {
|
|
||||||
toast({
|
toast({
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
title: '登录异常'
|
title: getErrText(error, '登录异常')
|
||||||
});
|
});
|
||||||
return setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.replace('/login');
|
router.replace('/login');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
loginSuccess(res);
|
},
|
||||||
} catch (error) {
|
[loginStore, loginSuccess, router, toast]
|
||||||
|
);
|
||||||
|
|
||||||
|
useQuery(['init', code], () => {
|
||||||
|
if (!code) return;
|
||||||
|
if (state !== loginStore?.state) {
|
||||||
toast({
|
toast({
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
title: getErrText(error, '登录异常')
|
title: '安全校验失败'
|
||||||
});
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.replace('/login');
|
router.replace('/login');
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}, [code, loginStore, loginSuccess]);
|
authCode(code);
|
||||||
|
|
||||||
useQuery(['init', code], () => {
|
|
||||||
authCode();
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,6 +95,7 @@ export async function getServerSideProps(content: any) {
|
|||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
code: content?.query?.code,
|
code: content?.query?.code,
|
||||||
|
state: content?.query?.state,
|
||||||
...(await serviceSideProps(content))
|
...(await serviceSideProps(content))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
24
client/src/service/common/ipLimit/schema.ts
Normal file
24
client/src/service/common/ipLimit/schema.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Schema, model, models, Model } from 'mongoose';
|
||||||
|
import type { IpLimitSchemaType } from '@/types/common/ipLimit';
|
||||||
|
|
||||||
|
const IpLimitSchema = new Schema({
|
||||||
|
eventId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
ip: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
lastMinute: {
|
||||||
|
type: Date,
|
||||||
|
default: () => new Date()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IpLimit: Model<IpLimitSchemaType> =
|
||||||
|
models['ip_limit'] || model('ip_limit', IpLimitSchema);
|
@@ -133,7 +133,7 @@ export * from './models/trainingData';
|
|||||||
export * from './models/openapi';
|
export * from './models/openapi';
|
||||||
export * from './models/promotionRecord';
|
export * from './models/promotionRecord';
|
||||||
export * from './models/collection';
|
export * from './models/collection';
|
||||||
export * from './models/outLink';
|
|
||||||
export * from './models/kb';
|
export * from './models/kb';
|
||||||
export * from './models/inform';
|
export * from './models/inform';
|
||||||
export * from './models/image';
|
export * from './models/image';
|
||||||
|
export * from './support/outLink/schema';
|
||||||
|
79
client/src/service/support/outLink/auth.ts
Normal file
79
client/src/service/support/outLink/auth.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { PRICE_SCALE } from '@/constants/common';
|
||||||
|
import { IpLimit } from '@/service/common/ipLimit/schema';
|
||||||
|
import { authBalanceByUid, AuthUserTypeEnum } from '@/service/utils/auth';
|
||||||
|
import { OutLinkSchema } from '@/types/support/outLink';
|
||||||
|
import { OutLink } from './schema';
|
||||||
|
|
||||||
|
export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: string | null }) {
|
||||||
|
// get outLink
|
||||||
|
const outLink = await OutLink.findOne({
|
||||||
|
shareId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!outLink) {
|
||||||
|
return Promise.reject('分享链接无效');
|
||||||
|
}
|
||||||
|
|
||||||
|
const uid = String(outLink.userId);
|
||||||
|
|
||||||
|
// authBalance
|
||||||
|
const user = await authBalanceByUid(uid);
|
||||||
|
|
||||||
|
// limit auth
|
||||||
|
await authOutLinkLimit({ outLink, ip });
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
userId: String(outLink.userId),
|
||||||
|
appId: String(outLink.appId),
|
||||||
|
authType: AuthUserTypeEnum.token,
|
||||||
|
responseDetail: outLink.responseDetail
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function authOutLinkLimit({
|
||||||
|
outLink,
|
||||||
|
ip
|
||||||
|
}: {
|
||||||
|
outLink: OutLinkSchema;
|
||||||
|
ip?: string | null;
|
||||||
|
}) {
|
||||||
|
if (!ip || !outLink.limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outLink.limit.expiredTime && outLink.limit.expiredTime.getTime() < Date.now()) {
|
||||||
|
return Promise.reject('分享链接已过期');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outLink.limit.credit > -1 && outLink.total > outLink.limit.credit * PRICE_SCALE) {
|
||||||
|
return Promise.reject('链接超出使用限制');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!ipLimit) {
|
||||||
|
await IpLimit.create({
|
||||||
|
eventId: outLink._id,
|
||||||
|
ip,
|
||||||
|
account: outLink.limit.QPM - 1
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// over one minute
|
||||||
|
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
|
||||||
|
if (diffTime >= 60 * 1000) {
|
||||||
|
ipLimit.account = outLink.limit.QPM - 1;
|
||||||
|
ipLimit.lastMinute = new Date();
|
||||||
|
return await ipLimit.save();
|
||||||
|
}
|
||||||
|
if (ipLimit.account <= 0) {
|
||||||
|
return Promise.reject(
|
||||||
|
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ipLimit.account = ipLimit.account - 1;
|
||||||
|
await ipLimit.save();
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
import { Schema, model, models, Model } from 'mongoose';
|
import { Schema, model, models, Model } from 'mongoose';
|
||||||
import { OutLinkSchema as SchmaType } from '@/types/mongoSchema';
|
import { OutLinkSchema as SchemaType } from '@/types/support/outLink';
|
||||||
import { OutLinkTypeEnum } from '@/constants/chat';
|
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||||
|
|
||||||
const OutLinkSchema = new Schema({
|
const OutLinkSchema = new Schema({
|
||||||
@@ -26,12 +26,30 @@ const OutLinkSchema = new Schema({
|
|||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
total: {
|
total: {
|
||||||
|
// total amount
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
lastTime: {
|
lastTime: {
|
||||||
type: Date
|
type: Date
|
||||||
|
},
|
||||||
|
responseDetail: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
expiredTime: {
|
||||||
|
type: Date
|
||||||
|
},
|
||||||
|
QPM: {
|
||||||
|
type: Number,
|
||||||
|
default: 1000
|
||||||
|
},
|
||||||
|
credit: {
|
||||||
|
type: Number,
|
||||||
|
default: -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const OutLink: Model<SchmaType> = models['outlinks'] || model('outlinks', OutLinkSchema);
|
export const OutLink: Model<SchemaType> = models['outlinks'] || model('outlinks', OutLinkSchema);
|
@@ -208,24 +208,3 @@ export const authKb = async ({ kbId, userId }: { kbId: string; userId: string })
|
|||||||
}
|
}
|
||||||
return Promise.reject(ERROR_ENUM.unAuthKb);
|
return Promise.reject(ERROR_ENUM.unAuthKb);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authShareChat = async ({ shareId }: { shareId: string }) => {
|
|
||||||
// get shareChat
|
|
||||||
const shareChat = await OutLink.findOne({ shareId });
|
|
||||||
|
|
||||||
if (!shareChat) {
|
|
||||||
return Promise.reject('分享链接已失效');
|
|
||||||
}
|
|
||||||
|
|
||||||
const uid = String(shareChat.userId);
|
|
||||||
|
|
||||||
// authBalance
|
|
||||||
const user = await authBalanceByUid(uid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
userId: String(shareChat.userId),
|
|
||||||
appId: String(shareChat.appId),
|
|
||||||
authType: AuthUserTypeEnum.token
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
@@ -2,8 +2,9 @@ import { create } from 'zustand';
|
|||||||
import { devtools, persist } from 'zustand/middleware';
|
import { devtools, persist } from 'zustand/middleware';
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from 'zustand/middleware/immer';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import { OAuthEnum } from '@/constants/user';
|
||||||
|
|
||||||
type LoginStoreType = { provider: 'git'; lastRoute: string };
|
type LoginStoreType = { provider: `${OAuthEnum}`; lastRoute: string; state: string };
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
lastRoute: string;
|
lastRoute: string;
|
||||||
|
4
client/src/types/app.d.ts
vendored
4
client/src/types/app.d.ts
vendored
@@ -38,10 +38,6 @@ export interface ShareAppItem {
|
|||||||
isCollection: boolean;
|
isCollection: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ShareChatEditType = {
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* agent */
|
/* agent */
|
||||||
/* question classify */
|
/* question classify */
|
||||||
export type ClassifyQuestionAgentItemType = {
|
export type ClassifyQuestionAgentItemType = {
|
||||||
|
7
client/src/types/common/ipLimit.d.ts
vendored
Normal file
7
client/src/types/common/ipLimit.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export type IpLimitSchemaType = {
|
||||||
|
_id: string;
|
||||||
|
eventId: string;
|
||||||
|
ip: string;
|
||||||
|
account: number;
|
||||||
|
lastMinute: Date;
|
||||||
|
};
|
9
client/src/types/index.d.ts
vendored
9
client/src/types/index.d.ts
vendored
@@ -27,8 +27,13 @@ export type FeConfigsType = {
|
|||||||
authorText?: string;
|
authorText?: string;
|
||||||
beianText?: string;
|
beianText?: string;
|
||||||
googleClientVerKey?: string;
|
googleClientVerKey?: string;
|
||||||
gitLoginKey?: string;
|
oauth?: {
|
||||||
exportLimitMinutes?: number;
|
github?: string;
|
||||||
|
google?: string;
|
||||||
|
};
|
||||||
|
limit?: {
|
||||||
|
exportLimitMinutes?: number;
|
||||||
|
};
|
||||||
scripts?: { [key: string]: string }[];
|
scripts?: { [key: string]: string }[];
|
||||||
};
|
};
|
||||||
export type SystemEnvType = {
|
export type SystemEnvType = {
|
||||||
|
13
client/src/types/mongoSchema.d.ts
vendored
13
client/src/types/mongoSchema.d.ts
vendored
@@ -4,7 +4,7 @@ import type { DataType } from './data';
|
|||||||
import { BillSourceEnum, InformTypeEnum } from '@/constants/user';
|
import { BillSourceEnum, InformTypeEnum } from '@/constants/user';
|
||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
import type { AppModuleItemType } from './app';
|
import type { AppModuleItemType } from './app';
|
||||||
import { ChatSourceEnum, OutLinkTypeEnum } from '@/constants/chat';
|
import { ChatSourceEnum } from '@/constants/chat';
|
||||||
import { AppTypeEnum } from '@/constants/app';
|
import { AppTypeEnum } from '@/constants/app';
|
||||||
import { KbTypeEnum } from '@/constants/kb';
|
import { KbTypeEnum } from '@/constants/kb';
|
||||||
|
|
||||||
@@ -156,17 +156,6 @@ export interface PromotionRecordSchema {
|
|||||||
amount: number;
|
amount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OutLinkSchema {
|
|
||||||
_id: string;
|
|
||||||
shareId: string;
|
|
||||||
userId: string;
|
|
||||||
appId: string;
|
|
||||||
name: string;
|
|
||||||
total: number;
|
|
||||||
lastTime: Date;
|
|
||||||
type: `${OutLinkTypeEnum}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type kbSchema = {
|
export type kbSchema = {
|
||||||
_id: string;
|
_id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
25
client/src/types/support/outLink.d.ts
vendored
Normal file
25
client/src/types/support/outLink.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { OutLinkTypeEnum } from '@/constants/chat';
|
||||||
|
|
||||||
|
export interface OutLinkSchema {
|
||||||
|
_id: string;
|
||||||
|
shareId: string;
|
||||||
|
userId: string;
|
||||||
|
appId: string;
|
||||||
|
name: string;
|
||||||
|
total: number;
|
||||||
|
lastTime: Date;
|
||||||
|
type: `${OutLinkTypeEnum}`;
|
||||||
|
responseDetail: boolean;
|
||||||
|
limit?: {
|
||||||
|
expiredTime?: Date;
|
||||||
|
QPM: number;
|
||||||
|
credit: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OutLinkEditType = {
|
||||||
|
_id?: string;
|
||||||
|
name: string;
|
||||||
|
responseDetail: OutLinkSchema['responseDetail'];
|
||||||
|
limit: OutLinkSchema['limit'];
|
||||||
|
};
|
@@ -31,7 +31,6 @@ weight: 520
|
|||||||
"show_git": true, // 是否展示 Git
|
"show_git": true, // 是否展示 Git
|
||||||
"systemTitle": "FastGPT", // 系统的 title
|
"systemTitle": "FastGPT", // 系统的 title
|
||||||
"authorText": "Made by FastGPT Team.", // 签名
|
"authorText": "Made by FastGPT Team.", // 签名
|
||||||
"gitLoginKey": "" // Git 登录凭证
|
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
...
|
...
|
||||||
@@ -56,7 +55,6 @@ weight: 520
|
|||||||
"show_git": true,
|
"show_git": true,
|
||||||
"systemTitle": "FastGPT",
|
"systemTitle": "FastGPT",
|
||||||
"authorText": "Made by FastGPT Team.",
|
"authorText": "Made by FastGPT Team.",
|
||||||
"gitLoginKey": "",
|
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
"SystemParams": {
|
"SystemParams": {
|
||||||
|
@@ -9,7 +9,6 @@
|
|||||||
"show_doc": true,
|
"show_doc": true,
|
||||||
"systemTitle": "FastGPT",
|
"systemTitle": "FastGPT",
|
||||||
"authorText": "Made by FastGPT Team.",
|
"authorText": "Made by FastGPT Team.",
|
||||||
"gitLoginKey": "",
|
|
||||||
"scripts": []
|
"scripts": []
|
||||||
},
|
},
|
||||||
"SystemParams": {
|
"SystemParams": {
|
||||||
|
Reference in New Issue
Block a user