Add share link hook (#351)
This commit is contained in:
parent
9136c9306a
commit
63cd379682
BIN
docSite/assets/imgs/sharelinkProcess.png
Normal file
BIN
docSite/assets/imgs/sharelinkProcess.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 161 KiB |
@ -38,7 +38,7 @@ FastGPT 的 API Key 有 2 类,一类是全局通用的 key;一类是携带
|
|||||||
- headers.Authorization: Bearer apikey
|
- headers.Authorization: Bearer apikey
|
||||||
- chatId: string | undefined 。
|
- chatId: string | undefined 。
|
||||||
- 为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
- 为 undefined 时(不传入),不使用 FastGpt 提供的上下文功能,完全通过传入的 messages 构建上下文。 不会将你的记录存储到数据库中,你也无法在记录汇总中查阅到。
|
||||||
- 为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录。并拼接 messages 数组最后一个内容作为完整请求。(自行确保 chatid 唯一,长度不限)
|
- 为非空字符串时,意味着使用 chatId 进行对话,自动从 FastGpt 数据库取历史记录。并拼接 messages 数组最后一个内容作为完整请求。(自行确保 chatId 唯一,长度不限)
|
||||||
- messages: 与 openai gpt 接口完全一致。
|
- messages: 与 openai gpt 接口完全一致。
|
||||||
- detail: 是否返回详细值(模块状态,响应的完整结果),会通过event进行区分
|
- detail: 是否返回详细值(模块状态,响应的完整结果),会通过event进行区分
|
||||||
- variables: 变量。一个对象,效果同全局变量。
|
- variables: 变量。一个对象,效果同全局变量。
|
||||||
@ -227,7 +227,7 @@ data: [{"moduleName":"KB Search","price":1.2000000000000002,"model":"Embedding-2
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 往知识库添加数据
|
### 知识库添加数据
|
||||||
|
|
||||||
{{< tabs tabTotal="4" >}}
|
{{< tabs tabTotal="4" >}}
|
||||||
{{< tab tabName="请求示例" >}}
|
{{< tab tabName="请求示例" >}}
|
||||||
@ -241,10 +241,11 @@ curl --location --request POST 'https://fastgpt.run/api/core/dataset/data/pushDa
|
|||||||
"kbId": "64663f451ba1676dbdef0499",
|
"kbId": "64663f451ba1676dbdef0499",
|
||||||
"mode": "index",
|
"mode": "index",
|
||||||
"prompt": "qa 拆分引导词,index 模式下可以忽略",
|
"prompt": "qa 拆分引导词,index 模式下可以忽略",
|
||||||
|
"billId": "可选。如果有这个值,本次的数据会被聚合到一个订单中,这个值可以重复使用。可以参考 [创建训练订单] 获取该值。",
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"a": "test",
|
"a": "test",
|
||||||
"q": "1111"
|
"q": "1111",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"a": "test2",
|
"a": "test2",
|
||||||
@ -370,6 +371,158 @@ curl --location --request POST 'https://fastgpt.run/api/core/dataset/searchTest'
|
|||||||
|
|
||||||
{{< /tabs >}}
|
{{< /tabs >}}
|
||||||
|
|
||||||
|
## 订单
|
||||||
|
|
||||||
|
### 创建训练订单
|
||||||
|
|
||||||
|
**请求示例**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST 'https://fastgpt.run/api/common/bill/createTrainingBill' \
|
||||||
|
--header 'Authorization: Bearer {{apikey}}' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw ''
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应结果**
|
||||||
|
|
||||||
|
data 为 billId,可用于 api 添加数据时进行账单聚合。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"statusText": "",
|
||||||
|
"message": "",
|
||||||
|
"data": "65112ab717c32018f4156361"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 免登录分享链接校验(内测中)
|
||||||
|
|
||||||
|
免登录链接配置中,增加了`凭证校验服务器`后,使用分享链接时会向服务器发起请求,校验链接是否可用,并在每次对话结束后,向服务器发送对话结果。下面以`host`来表示`凭证校验服务器`。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "错误提示"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 分享链接中增加额外 query
|
||||||
|
|
||||||
|
增加一个 query: authToken。例如:
|
||||||
|
|
||||||
|
原始的链接:https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192
|
||||||
|
完整链接: https://fastgpt.run/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345
|
||||||
|
|
||||||
|
发出校验请求时候,会在`body`中携带 token={{authToken}} 的参数。
|
||||||
|
|
||||||
|
### 初始化校验
|
||||||
|
|
||||||
|
**FastGPT 发出的请求**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST '{{host}}/shareAuth/init' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"token": "sintdolore"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 对话前校验
|
||||||
|
|
||||||
|
**FastGPT 发出的请求**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST '{{host}}/shareAuth/start' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"token": "sintdolore",
|
||||||
|
"question": "用户问题",
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 对话结果上报
|
||||||
|
|
||||||
|
**FastGPT 发出的请求**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request POST '{{host}}/shareAuth/finish' \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data-raw '{
|
||||||
|
"token": "sint dolore",
|
||||||
|
"responseData": [
|
||||||
|
{
|
||||||
|
"moduleName": "KB Search",
|
||||||
|
"price": 1.2000000000000002,
|
||||||
|
"model": "Embedding-2",
|
||||||
|
"tokens": 6,
|
||||||
|
"similarity": 0.61,
|
||||||
|
"limit": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"moduleName": "AI Chat",
|
||||||
|
"price": 454.5,
|
||||||
|
"model": "FastAI-4k",
|
||||||
|
"tokens": 303,
|
||||||
|
"question": "导演是谁",
|
||||||
|
"answer": "电影《铃芽之旅》的导演是新海诚。",
|
||||||
|
"maxToken": 2050,
|
||||||
|
"quoteList": [
|
||||||
|
{
|
||||||
|
"kb_id": "646627f4f7b896cfd8910e38",
|
||||||
|
"id": "8099",
|
||||||
|
"q": "本作的主人公是谁?",
|
||||||
|
"a": "本作的主人公是名叫铃芽的少女。",
|
||||||
|
"source": "手动修改"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kb_id": "646627f4f7b896cfd8910e38",
|
||||||
|
"id": "8686",
|
||||||
|
"q": "电影《铃芽之旅》男主角是谁?",
|
||||||
|
"a": "电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。",
|
||||||
|
"source": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kb_id": "646627f4f7b896cfd8910e38",
|
||||||
|
"id": "19339",
|
||||||
|
"q": "电影《铃芽之旅》的导演是谁?22",
|
||||||
|
"a": "电影《铃芽之旅》的导演是新海诚。",
|
||||||
|
"source": "手动修改"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"completeMessages": [
|
||||||
|
{
|
||||||
|
"obj": "System",
|
||||||
|
"value": "下面是知识库内容:\n1. [本作的主人公是谁?\n本作的主人公是名叫铃芽的少女。]\n2. [电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。]\n3. [电影《铃芽之旅》的导演是谁?22\n电影《铃芽之旅》的导演是新海诚。]\n"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"obj": "System",
|
||||||
|
"value": "1.请记住,你的身份是百度的下一代知识增强语言模型,能够完全根据知识库提供的内容回答问题。\n\n2. 你忘记了关于电影《铃芽之旅》以外的内容。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"obj": "System",
|
||||||
|
"value": "你仅回答关于电影《玲芽之旅》的问题,其余问题直接回复: 我不清楚。"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"obj": "Human",
|
||||||
|
"value": "导演是谁"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"obj": "AI",
|
||||||
|
"value": "电影《铃芽之旅》的导演是新海诚。"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
响应值与 chat 接口相同,增加了一个 token。可以重点关注`responseData`里的值,price 与实际价格的倍率为`100000`。
|
||||||
|
|
||||||
|
**此接口无需响应值**
|
||||||
|
|
||||||
# 使用案例
|
# 使用案例
|
||||||
|
|
||||||
|
|||||||
@ -248,7 +248,10 @@
|
|||||||
"QPM Tips": "The maximum number of queries per IP address per minute",
|
"QPM Tips": "The maximum number of queries per IP address per minute",
|
||||||
"QPM is empty": "QPM is empty",
|
"QPM is empty": "QPM is empty",
|
||||||
"Response Detail": "Quote",
|
"Response Detail": "Quote",
|
||||||
"Response Detail tips": "Whether detailed data such as references to be returned"
|
"Response Detail tips": "Whether detailed data such as references to be returned",
|
||||||
|
"token auth": "Token Auth",
|
||||||
|
"token auth Tips": "Identity verification server address. If this value is set, the server will be specified to send a request for identity verification before each session",
|
||||||
|
"token auth use cases": "Review the authentication instructions"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"Help Document": "Document"
|
"Help Document": "Document"
|
||||||
|
|||||||
@ -248,7 +248,10 @@
|
|||||||
"QPM Tips": "每个 IP 每分钟最多提问多少次",
|
"QPM Tips": "每个 IP 每分钟最多提问多少次",
|
||||||
"QPM is empty": "QPM 不能为空",
|
"QPM is empty": "QPM 不能为空",
|
||||||
"Response Detail": "返回详情",
|
"Response Detail": "返回详情",
|
||||||
"Response Detail tips": "是否需要返回详情(引用内容,调用时间等,不会返回预设提示词和完整上下文)"
|
"Response Detail tips": "是否需要返回详情(引用内容,调用时间等,不会返回预设提示词和完整上下文)",
|
||||||
|
"token auth": "身份验证",
|
||||||
|
"token auth Tips": "身份校验服务器地址,如填写该值,每次对话前都会想指定服务器发送一个请求,进行身份校验",
|
||||||
|
"token auth use cases": "查看身份验证使用说明"
|
||||||
},
|
},
|
||||||
"system": {
|
"system": {
|
||||||
"Help Document": "帮助文档"
|
"Help Document": "帮助文档"
|
||||||
|
|||||||
3
projects/app/src/api/core/dataset/data.d.ts
vendored
3
projects/app/src/api/core/dataset/data.d.ts
vendored
@ -1,10 +1,11 @@
|
|||||||
import { KbTypeEnum } from '@/constants/dataset';
|
import { KbTypeEnum } from '@/constants/dataset';
|
||||||
import type { RequestPaging } from '@/types';
|
import type { RequestPaging } from '@/types';
|
||||||
import { TrainingModeEnum } from '@/constants/plugin';
|
import { TrainingModeEnum } from '@/constants/plugin';
|
||||||
|
import { DatasetDataItemType } from '@/types/core/dataset/data';
|
||||||
|
|
||||||
export type PushDataProps = {
|
export type PushDataProps = {
|
||||||
kbId: string;
|
kbId: string;
|
||||||
data: DatasetItemType[];
|
data: DatasetDataItemType[];
|
||||||
mode: `${TrainingModeEnum}`;
|
mode: `${TrainingModeEnum}`;
|
||||||
prompt?: string;
|
prompt?: string;
|
||||||
billId?: string;
|
billId?: string;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type { OutLinkSchema } from '@/types/support/outLink';
|
|||||||
/**
|
/**
|
||||||
* 初始化分享聊天
|
* 初始化分享聊天
|
||||||
*/
|
*/
|
||||||
export const initShareChatInfo = (data: { shareId: string }) =>
|
export const initShareChatInfo = (data: { shareId: string; authToken?: string }) =>
|
||||||
GET<InitShareChatResponse>(`/support/outLink/init`, data);
|
GET<InitShareChatResponse>(`/support/outLink/init`, data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -139,6 +139,7 @@ const ChatBox = (
|
|||||||
userAvatar,
|
userAvatar,
|
||||||
variableModules,
|
variableModules,
|
||||||
welcomeText,
|
welcomeText,
|
||||||
|
active = true,
|
||||||
onUpdateVariable,
|
onUpdateVariable,
|
||||||
onStartChat,
|
onStartChat,
|
||||||
onDelMessage
|
onDelMessage
|
||||||
@ -152,6 +153,7 @@ const ChatBox = (
|
|||||||
userAvatar?: string;
|
userAvatar?: string;
|
||||||
variableModules?: VariableItemType[];
|
variableModules?: VariableItemType[];
|
||||||
welcomeText?: string;
|
welcomeText?: string;
|
||||||
|
active?: boolean;
|
||||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||||
onStartChat?: (e: StartChatFnProps) => Promise<{
|
onStartChat?: (e: StartChatFnProps) => Promise<{
|
||||||
responseText: string;
|
responseText: string;
|
||||||
@ -860,7 +862,7 @@ const ChatBox = (
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{/* input */}
|
{/* input */}
|
||||||
{onStartChat && variableIsFinish ? (
|
{onStartChat && variableIsFinish && active ? (
|
||||||
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
|
<Box m={['0 auto', '10px auto']} w={'100%'} maxW={['auto', 'min(750px, 100%)']} px={[0, 5]}>
|
||||||
<Box
|
<Box
|
||||||
py={'18px'}
|
py={'18px'}
|
||||||
|
|||||||
@ -40,7 +40,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
/* user auth */
|
/* user auth */
|
||||||
const { userId, user } = await authUser({ req, authBalance: true });
|
const { userId, user } = await authUser({ req, authToken: true, authBalance: true });
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('user not found');
|
throw new Error('user not found');
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
try {
|
try {
|
||||||
const { name } = req.body as CreateTrainingBillType;
|
const { name } = req.body as CreateTrainingBillType;
|
||||||
|
|
||||||
const { userId } = await authUser({ req, authToken: true });
|
const { userId } = await authUser({ req, authToken: true, authApiKey: true });
|
||||||
|
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
await PgClient.delete(PgDatasetTableName, {
|
await PgClient.delete(PgDatasetTableName, {
|
||||||
where: [['user_id', userId], 'AND', ['id', dataId]]
|
where: [['user_id', userId], 'AND', ['id', dataId]]
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
jsonRes(res, {
|
jsonRes(res, {
|
||||||
data: await getVectorAndInsertDataset({
|
data: await getVectorAndInsertDataset({
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true, authApiKey: true });
|
||||||
|
|
||||||
jsonRes<PushDataResponse>(res, {
|
jsonRes<PushDataResponse>(res, {
|
||||||
data: await pushDataToKb({
|
data: await pushDataToKb({
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
|
|
||||||
// auth user and get kb
|
// auth user and get kb
|
||||||
const [{ userId }, kb] = await Promise.all([
|
const [{ userId }, kb] = await Promise.all([
|
||||||
authUser({ req }),
|
authUser({ req, authToken: true }),
|
||||||
KB.findById(kbId, 'vectorModel')
|
KB.findById(kbId, 'vectorModel')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
|
|
||||||
// 凭证校验
|
// 凭证校验
|
||||||
const [{ userId }, kb] = await Promise.all([
|
const [{ userId }, kb] = await Promise.all([
|
||||||
authUser({ req }),
|
authUser({ req, authToken: true, authApiKey: true }),
|
||||||
KB.findById(kbId, 'vectorModel')
|
KB.findById(kbId, 'vectorModel')
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ type Response = {
|
|||||||
|
|
||||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
try {
|
try {
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
let { input, model } = req.query as Props;
|
let { input, model } = req.query as Props;
|
||||||
|
|
||||||
if (!Array.isArray(input)) {
|
if (!Array.isArray(input)) {
|
||||||
|
|||||||
@ -34,7 +34,7 @@ import requestIp from 'request-ip';
|
|||||||
import { replaceVariable } from '@/utils/common/tools/text';
|
import { replaceVariable } from '@/utils/common/tools/text';
|
||||||
import { ModuleDispatchProps } from '@/types/core/modules';
|
import { ModuleDispatchProps } from '@/types/core/modules';
|
||||||
import { selectShareResponse } from '@/utils/service/core/chat';
|
import { selectShareResponse } from '@/utils/service/core/chat';
|
||||||
import { updateOutLinkUsage } from '@/service/support/outLink';
|
import { pushResult2Remote, updateOutLinkUsage } from '@/service/support/outLink';
|
||||||
import { updateApiKeyUsage } from '@/service/support/openapi';
|
import { updateApiKeyUsage } from '@/service/support/openapi';
|
||||||
|
|
||||||
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
||||||
@ -44,6 +44,7 @@ type FastGptWebChatProps = {
|
|||||||
};
|
};
|
||||||
type FastGptShareChatProps = {
|
type FastGptShareChatProps = {
|
||||||
shareId?: string;
|
shareId?: string;
|
||||||
|
authToken?: string;
|
||||||
};
|
};
|
||||||
export type Props = CreateChatCompletionRequest &
|
export type Props = CreateChatCompletionRequest &
|
||||||
FastGptWebChatProps &
|
FastGptWebChatProps &
|
||||||
@ -71,6 +72,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
chatId,
|
chatId,
|
||||||
appId,
|
appId,
|
||||||
shareId,
|
shareId,
|
||||||
|
authToken,
|
||||||
stream = false,
|
stream = false,
|
||||||
detail = false,
|
detail = false,
|
||||||
messages = [],
|
messages = [],
|
||||||
@ -111,10 +113,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
if (shareId) {
|
if (shareId) {
|
||||||
return authOutLinkChat({
|
return authOutLinkChat({
|
||||||
shareId,
|
shareId,
|
||||||
ip: requestIp.getClientIp(req)
|
ip: requestIp.getClientIp(req),
|
||||||
|
authToken,
|
||||||
|
question:
|
||||||
|
(messages[messages.length - 2]?.role === 'user'
|
||||||
|
? messages[messages.length - 2].content
|
||||||
|
: messages[messages.length - 1]?.content) || ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return authUser({ req, authBalance: true });
|
return authUser({ req, authToken: true, authApiKey: true, authBalance: true });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@ -260,11 +267,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
|||||||
response: responseData
|
response: responseData
|
||||||
});
|
});
|
||||||
|
|
||||||
!!shareId &&
|
if (shareId) {
|
||||||
|
pushResult2Remote({ authToken, shareId, responseData });
|
||||||
updateOutLinkUsage({
|
updateOutLinkUsage({
|
||||||
shareId,
|
shareId,
|
||||||
total
|
total
|
||||||
});
|
});
|
||||||
|
}
|
||||||
!!apikey &&
|
!!apikey &&
|
||||||
updateApiKeyUsage({
|
updateApiKeyUsage({
|
||||||
apikey,
|
apikey,
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export type Response = { history: ChatItemType[] };
|
|||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
try {
|
try {
|
||||||
await connectToDatabase();
|
await connectToDatabase();
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
const { chatId, limit } = req.body as Props;
|
const { chatId, limit } = req.body as Props;
|
||||||
|
|
||||||
jsonRes<Response>(res, {
|
jsonRes<Response>(res, {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const fetchContent = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
throw new Error('urlList is empty');
|
throw new Error('urlList is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
await authUser({ req });
|
await authUser({ req, authToken: true });
|
||||||
|
|
||||||
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
|
urlList = urlList.filter((url) => /^(http|https):\/\/[^ "]+$/.test(url));
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
throw new Error('fileId is empty');
|
throw new Error('fileId is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
const gridFs = new GridFSStorage('dataset', userId);
|
const gridFs = new GridFSStorage('dataset', userId);
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
|||||||
throw new Error('fileId is empty');
|
throw new Error('fileId is empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { userId } = await authUser({ req });
|
const { userId } = await authUser({ req, authToken: true });
|
||||||
|
|
||||||
// auth file
|
// auth file
|
||||||
const gridFs = new GridFSStorage('dataset', userId);
|
const gridFs = new GridFSStorage('dataset', userId);
|
||||||
|
|||||||
@ -5,12 +5,14 @@ import type { InitShareChatResponse } from '@/api/response/chat';
|
|||||||
import { authApp } from '@/service/utils/auth';
|
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';
|
||||||
|
import { authShareChatInit } from '@/service/support/outLink/auth';
|
||||||
|
|
||||||
/* init share chat window */
|
/* 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, authToken } = req.query as {
|
||||||
shareId: string;
|
shareId: string;
|
||||||
|
authToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!shareId) {
|
if (!shareId) {
|
||||||
@ -36,7 +38,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
userId: String(shareChat.userId),
|
userId: String(shareChat.userId),
|
||||||
authOwner: false
|
authOwner: false
|
||||||
}),
|
}),
|
||||||
User.findById(shareChat.userId, 'avatar')
|
User.findById(shareChat.userId, 'avatar'),
|
||||||
|
authShareChatInit(authToken, shareChat.limit?.hookUrl)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
jsonRes<InitShareChatResponse>(res, {
|
jsonRes<InitShareChatResponse>(res, {
|
||||||
|
|||||||
@ -17,7 +17,8 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
MenuButton,
|
MenuButton,
|
||||||
MenuList,
|
MenuList,
|
||||||
MenuItem
|
MenuItem,
|
||||||
|
Link
|
||||||
} 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';
|
||||||
@ -58,7 +59,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position={'relative'} pt={[3, 5, 8]} px={[5, 8]} minH={'50vh'}>
|
<Box position={'relative'} pt={[3, 5, 8]} px={[2, 8]} minH={'50vh'}>
|
||||||
<Flex justifyContent={'space-between'}>
|
<Flex justifyContent={'space-between'}>
|
||||||
<Box fontWeight={'bold'}>
|
<Box fontWeight={'bold'}>
|
||||||
免登录窗口
|
免登录窗口
|
||||||
@ -85,7 +86,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
<TableContainer mt={3}>
|
<TableContainer mt={3}>
|
||||||
<Table variant={'simple'} w={'100%'} overflowX={'auto'}>
|
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>名称</Th>
|
<Th>名称</Th>
|
||||||
@ -96,6 +97,7 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Th>金额限制(¥)</Th>
|
<Th>金额限制(¥)</Th>
|
||||||
<Th>IP限流(人/分钟)</Th>
|
<Th>IP限流(人/分钟)</Th>
|
||||||
<Th>过期时间</Th>
|
<Th>过期时间</Th>
|
||||||
|
<Th>token校验</Th>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Th>最后使用时间</Th>
|
<Th>最后使用时间</Th>
|
||||||
@ -113,12 +115,13 @@ const Share = ({ appId }: { appId: string }) => {
|
|||||||
<Td>
|
<Td>
|
||||||
{item.limit && item.limit.credit > -1 ? `${item.limit.credit}元` : '无限制'}
|
{item.limit && item.limit.credit > -1 ? `${item.limit.credit}元` : '无限制'}
|
||||||
</Td>
|
</Td>
|
||||||
<Td>{item.limit?.QPM || '-'}</Td>
|
<Td>{item?.limit?.QPM || '-'}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{item.limit?.expiredTime
|
{item?.limit?.expiredTime
|
||||||
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm')
|
||||||
: '-'}
|
: '-'}
|
||||||
</Td>
|
</Td>
|
||||||
|
<Th>{item?.limit?.hookUrl ? '✔' : '✖'}</Th>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
|
||||||
@ -267,7 +270,6 @@ function EditLinkModal({
|
|||||||
});
|
});
|
||||||
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
const { mutate: onclickUpdate, isLoading: updating } = useRequest({
|
||||||
mutationFn: (e: OutLinkEditType) => {
|
mutationFn: (e: OutLinkEditType) => {
|
||||||
console.log(e);
|
|
||||||
return putShareChat(e);
|
return putShareChat(e);
|
||||||
},
|
},
|
||||||
errorToast: '更新链接异常',
|
errorToast: '更新链接异常',
|
||||||
@ -338,6 +340,26 @@ function EditLinkModal({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Flex alignItems={'center'} mt={4}>
|
||||||
|
<Flex flex={'0 0 90px'}>
|
||||||
|
{t('outlink.token auth')}
|
||||||
|
<MyTooltip label={t('outlink.token auth Tips') || ''}>
|
||||||
|
<QuestionOutlineIcon ml={1} />
|
||||||
|
</MyTooltip>
|
||||||
|
</Flex>
|
||||||
|
<Input
|
||||||
|
placeholder={t('outlink.token auth Tips') || ''}
|
||||||
|
{...register('limit.hookUrl')}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Link
|
||||||
|
href="https://doc.fastgpt.run/docs/development/openapi/#分享链接中增加额外-query"
|
||||||
|
target={'_blank'}
|
||||||
|
fontSize={'sm'}
|
||||||
|
color={'myGray.500'}
|
||||||
|
>
|
||||||
|
{t('outlink.token auth use cases')}
|
||||||
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -21,11 +21,20 @@ import ChatHeader from './components/ChatHeader';
|
|||||||
import ChatHistorySlider from './components/ChatHistorySlider';
|
import ChatHistorySlider from './components/ChatHistorySlider';
|
||||||
import { serviceSideProps } from '@/utils/web/i18n';
|
import { serviceSideProps } from '@/utils/web/i18n';
|
||||||
|
|
||||||
const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
const OutLink = ({
|
||||||
|
shareId,
|
||||||
|
chatId,
|
||||||
|
authToken
|
||||||
|
}: {
|
||||||
|
shareId: string;
|
||||||
|
chatId: string;
|
||||||
|
authToken?: string;
|
||||||
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
const { isOpen: isOpenSlider, onClose: onCloseSlider, onOpen: onOpenSlider } = useDisclosure();
|
||||||
const { isPc } = useGlobalStore();
|
const { isPc } = useGlobalStore();
|
||||||
|
const forbidRefresh = useRef(false);
|
||||||
|
|
||||||
const ChatBoxRef = useRef<ComponentRef>(null);
|
const ChatBoxRef = useRef<ComponentRef>(null);
|
||||||
|
|
||||||
@ -53,7 +62,8 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
messages: prompts,
|
messages: prompts,
|
||||||
variables,
|
variables,
|
||||||
shareId,
|
shareId,
|
||||||
chatId: completionChatId
|
chatId: completionChatId,
|
||||||
|
authToken
|
||||||
},
|
},
|
||||||
onMessage: generatingMessage,
|
onMessage: generatingMessage,
|
||||||
abortSignal: controller
|
abortSignal: controller
|
||||||
@ -75,10 +85,12 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (completionChatId !== chatId && controller.signal.reason !== 'leave') {
|
if (completionChatId !== chatId && controller.signal.reason !== 'leave') {
|
||||||
|
forbidRefresh.current = true;
|
||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
shareId,
|
shareId,
|
||||||
chatId: completionChatId
|
chatId: completionChatId,
|
||||||
|
authToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -96,11 +108,11 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
|
|
||||||
return { responseText, responseData };
|
return { responseText, responseData };
|
||||||
},
|
},
|
||||||
[chatId, router, saveChatResponse, shareId]
|
[authToken, chatId, router, saveChatResponse, shareId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadAppInfo = useCallback(
|
const loadAppInfo = useCallback(
|
||||||
async (shareId: string, chatId: string) => {
|
async (shareId: string, chatId: string, authToken?: string) => {
|
||||||
if (!shareId) return null;
|
if (!shareId) return null;
|
||||||
const history = shareChatHistory.find((item) => item.chatId === chatId) || defaultHistory;
|
const history = shareChatHistory.find((item) => item.chatId === chatId) || defaultHistory;
|
||||||
|
|
||||||
@ -111,7 +123,8 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
const chatData = await (async () => {
|
const chatData = await (async () => {
|
||||||
if (shareChatData.app.name === '') {
|
if (shareChatData.app.name === '') {
|
||||||
return initShareChatInfo({
|
return initShareChatInfo({
|
||||||
shareId
|
shareId,
|
||||||
|
authToken
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return shareChatData;
|
return shareChatData;
|
||||||
@ -142,8 +155,12 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
[delManyShareChatHistoryByShareId, setShareChatData, shareChatData, shareChatHistory, toast]
|
[delManyShareChatHistoryByShareId, setShareChatData, shareChatData, shareChatHistory, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
useQuery(['init', shareId, chatId], () => {
|
useQuery(['init', shareId, chatId, authToken], () => {
|
||||||
return loadAppInfo(shareId, chatId);
|
if (forbidRefresh.current) {
|
||||||
|
forbidRefresh.current = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return loadAppInfo(shareId, chatId, authToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -185,7 +202,8 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
chatId: chatId || '',
|
chatId: chatId || '',
|
||||||
shareId
|
shareId,
|
||||||
|
authToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!isPc) {
|
if (!isPc) {
|
||||||
@ -197,7 +215,8 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
delManyShareChatHistoryByShareId(shareId);
|
delManyShareChatHistoryByShareId(shareId);
|
||||||
router.replace({
|
router.replace({
|
||||||
query: {
|
query: {
|
||||||
shareId
|
shareId,
|
||||||
|
authToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@ -222,6 +241,7 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
{/* chat box */}
|
{/* chat box */}
|
||||||
<Box flex={1}>
|
<Box flex={1}>
|
||||||
<ChatBox
|
<ChatBox
|
||||||
|
active={!!shareChatData.app.name}
|
||||||
ref={ChatBoxRef}
|
ref={ChatBoxRef}
|
||||||
appAvatar={shareChatData.app.avatar}
|
appAvatar={shareChatData.app.avatar}
|
||||||
userAvatar={shareChatData.userAvatar}
|
userAvatar={shareChatData.userAvatar}
|
||||||
@ -252,9 +272,10 @@ const OutLink = ({ shareId, chatId }: { shareId: string; chatId: string }) => {
|
|||||||
export async function getServerSideProps(context: any) {
|
export async function getServerSideProps(context: any) {
|
||||||
const shareId = context?.query?.shareId || '';
|
const shareId = context?.query?.shareId || '';
|
||||||
const chatId = context?.query?.chatId || '';
|
const chatId = context?.query?.chatId || '';
|
||||||
|
const authToken = context?.query?.authToken || '';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: { shareId, chatId, ...(await serviceSideProps(context)) }
|
props: { shareId, chatId, authToken, ...(await serviceSideProps(context)) }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,18 @@ import { IpLimit } from '@/service/common/ipLimit/schema';
|
|||||||
import { authBalanceByUid, AuthUserTypeEnum } from '@/service/utils/auth';
|
import { authBalanceByUid, AuthUserTypeEnum } from '@/service/utils/auth';
|
||||||
import { OutLinkSchema } from '@/types/support/outLink';
|
import { OutLinkSchema } from '@/types/support/outLink';
|
||||||
import { OutLink } from './schema';
|
import { OutLink } from './schema';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: string | null }) {
|
type AuthLinkProps = { ip?: string | null; authToken?: string; question: string };
|
||||||
|
|
||||||
|
export async function authOutLinkChat({
|
||||||
|
shareId,
|
||||||
|
ip,
|
||||||
|
authToken,
|
||||||
|
question
|
||||||
|
}: AuthLinkProps & {
|
||||||
|
shareId: string;
|
||||||
|
}) {
|
||||||
// get outLink
|
// get outLink
|
||||||
const outLink = await OutLink.findOne({
|
const outLink = await OutLink.findOne({
|
||||||
shareId
|
shareId
|
||||||
@ -18,7 +28,7 @@ export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: s
|
|||||||
|
|
||||||
const [user] = await Promise.all([
|
const [user] = await Promise.all([
|
||||||
authBalanceByUid(uid), // authBalance
|
authBalanceByUid(uid), // authBalance
|
||||||
...(global.feConfigs?.isPlus ? [authOutLinkLimit({ outLink, ip })] : []) // limit auth
|
...(global.feConfigs?.isPlus ? [authOutLinkLimit({ outLink, ip, authToken, question })] : []) // limit auth
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -32,10 +42,11 @@ export async function authOutLinkChat({ shareId, ip }: { shareId: string; ip?: s
|
|||||||
|
|
||||||
export async function authOutLinkLimit({
|
export async function authOutLinkLimit({
|
||||||
outLink,
|
outLink,
|
||||||
ip
|
ip,
|
||||||
}: {
|
authToken,
|
||||||
|
question
|
||||||
|
}: AuthLinkProps & {
|
||||||
outLink: OutLinkSchema;
|
outLink: OutLinkSchema;
|
||||||
ip?: string | null;
|
|
||||||
}) {
|
}) {
|
||||||
if (!ip || !outLink.limit) {
|
if (!ip || !outLink.limit) {
|
||||||
return;
|
return;
|
||||||
@ -49,30 +60,97 @@ export async function authOutLinkLimit({
|
|||||||
return Promise.reject('链接超出使用限制');
|
return Promise.reject('链接超出使用限制');
|
||||||
}
|
}
|
||||||
|
|
||||||
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
|
// ip limit
|
||||||
|
await (async () => {
|
||||||
try {
|
if (!outLink.limit) {
|
||||||
if (!ipLimit) {
|
|
||||||
await IpLimit.create({
|
|
||||||
eventId: outLink._id,
|
|
||||||
ip,
|
|
||||||
account: outLink.limit.QPM - 1
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// over one minute
|
try {
|
||||||
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
|
const ipLimit = await IpLimit.findOne({ ip, eventId: outLink._id });
|
||||||
if (diffTime >= 60 * 1000) {
|
|
||||||
ipLimit.account = outLink.limit.QPM - 1;
|
// first request
|
||||||
ipLimit.lastMinute = new Date();
|
if (!ipLimit) {
|
||||||
return await ipLimit.save();
|
return await IpLimit.create({
|
||||||
}
|
eventId: outLink._id,
|
||||||
if (ipLimit.account <= 0) {
|
ip,
|
||||||
return Promise.reject(
|
account: outLink.limit.QPM - 1
|
||||||
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
|
});
|
||||||
);
|
}
|
||||||
}
|
|
||||||
ipLimit.account = ipLimit.account - 1;
|
// over one minute
|
||||||
await ipLimit.save();
|
const diffTime = Date.now() - ipLimit.lastMinute.getTime();
|
||||||
} catch (error) {}
|
if (diffTime >= 60 * 1000) {
|
||||||
|
ipLimit.account = outLink.limit.QPM - 1;
|
||||||
|
ipLimit.lastMinute = new Date();
|
||||||
|
return await ipLimit.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// over limit
|
||||||
|
if (ipLimit.account <= 0) {
|
||||||
|
return Promise.reject(
|
||||||
|
`每分钟仅能请求 ${outLink.limit.QPM} 次, ${60 - Math.round(diffTime / 1000)}s 后重试~`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update limit
|
||||||
|
ipLimit.account = ipLimit.account - 1;
|
||||||
|
await ipLimit.save();
|
||||||
|
} catch (error) {}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// url auth. send request
|
||||||
|
await authShareStart({ authToken, tokenUrl: outLink.limit.hookUrl, question });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TokenAuthResponseType = {
|
||||||
|
success: boolean;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authShareChatInit = async (authToken?: string, tokenUrl?: string) => {
|
||||||
|
if (!tokenUrl || !global.feConfigs?.isPlus) return;
|
||||||
|
try {
|
||||||
|
const { data } = await axios<TokenAuthResponseType>({
|
||||||
|
baseURL: tokenUrl,
|
||||||
|
url: '/shareAuth/init',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
token: authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (data?.success !== true) {
|
||||||
|
return Promise.reject(data?.message || '身份校验失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject('身份校验失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authShareStart = async ({
|
||||||
|
tokenUrl,
|
||||||
|
authToken,
|
||||||
|
question
|
||||||
|
}: {
|
||||||
|
authToken?: string;
|
||||||
|
question: string;
|
||||||
|
tokenUrl?: string;
|
||||||
|
}) => {
|
||||||
|
if (!tokenUrl || !global.feConfigs?.isPlus) return;
|
||||||
|
try {
|
||||||
|
const { data } = await axios<TokenAuthResponseType>({
|
||||||
|
baseURL: tokenUrl,
|
||||||
|
url: '/shareAuth/start',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
token: authToken,
|
||||||
|
question
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.success !== true) {
|
||||||
|
return Promise.reject(data?.message || '身份校验失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject('身份校验失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { addLog } from '@/service/utils/tools';
|
import { addLog } from '@/service/utils/tools';
|
||||||
|
import { ChatHistoryItemResType } from '@/types/chat';
|
||||||
|
import axios from 'axios';
|
||||||
import { OutLink } from './schema';
|
import { OutLink } from './schema';
|
||||||
|
|
||||||
export const updateOutLinkUsage = async ({
|
export const updateOutLinkUsage = async ({
|
||||||
@ -20,3 +22,31 @@ export const updateOutLinkUsage = async ({
|
|||||||
addLog.error('update shareChat error', err);
|
addLog.error('update shareChat error', err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const pushResult2Remote = async ({
|
||||||
|
authToken,
|
||||||
|
shareId,
|
||||||
|
responseData
|
||||||
|
}: {
|
||||||
|
authToken?: string;
|
||||||
|
shareId?: string;
|
||||||
|
responseData?: ChatHistoryItemResType[];
|
||||||
|
}) => {
|
||||||
|
if (!shareId || !authToken) return;
|
||||||
|
try {
|
||||||
|
const outLink = await OutLink.findOne({
|
||||||
|
shareId
|
||||||
|
});
|
||||||
|
if (!outLink?.limit?.hookUrl) return;
|
||||||
|
|
||||||
|
axios({
|
||||||
|
method: 'post',
|
||||||
|
baseURL: outLink.limit.hookUrl,
|
||||||
|
url: '/shareAuth/finish',
|
||||||
|
data: {
|
||||||
|
token: authToken,
|
||||||
|
responseData
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|||||||
@ -48,6 +48,9 @@ const OutLinkSchema = new Schema({
|
|||||||
credit: {
|
credit: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: -1
|
default: -1
|
||||||
|
},
|
||||||
|
hookUrl: {
|
||||||
|
type: String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,18 +12,6 @@ export enum AuthUserTypeEnum {
|
|||||||
apikey = 'apikey'
|
apikey = 'apikey'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
|
|
||||||
// 获取 cookie
|
|
||||||
const cookies = Cookie.parse(cookie || '');
|
|
||||||
const cookieToken = cookies.token || token;
|
|
||||||
|
|
||||||
if (!cookieToken) {
|
|
||||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await authJWT(cookieToken);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* auth balance */
|
/* auth balance */
|
||||||
export const authBalanceByUid = async (uid: string) => {
|
export const authBalanceByUid = async (uid: string) => {
|
||||||
const user = await User.findById<UserModelSchema>(
|
const user = await User.findById<UserModelSchema>(
|
||||||
@ -45,13 +33,27 @@ export const authUser = async ({
|
|||||||
req,
|
req,
|
||||||
authToken = false,
|
authToken = false,
|
||||||
authRoot = false,
|
authRoot = false,
|
||||||
|
authApiKey = false,
|
||||||
authBalance = false
|
authBalance = false
|
||||||
}: {
|
}: {
|
||||||
req: NextApiRequest;
|
req: NextApiRequest;
|
||||||
authToken?: boolean;
|
authToken?: boolean;
|
||||||
authRoot?: boolean;
|
authRoot?: boolean;
|
||||||
|
authApiKey?: boolean;
|
||||||
authBalance?: boolean;
|
authBalance?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const authCookieToken = async (cookie?: string, token?: string): Promise<string> => {
|
||||||
|
// 获取 cookie
|
||||||
|
const cookies = Cookie.parse(cookie || '');
|
||||||
|
const cookieToken = cookies.token || token;
|
||||||
|
|
||||||
|
if (!cookieToken) {
|
||||||
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await authJWT(cookieToken);
|
||||||
|
};
|
||||||
|
// from authorization get apikey
|
||||||
const parseAuthorization = async (authorization?: string) => {
|
const parseAuthorization = async (authorization?: string) => {
|
||||||
if (!authorization) {
|
if (!authorization) {
|
||||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
@ -89,6 +91,7 @@ export const authUser = async ({
|
|||||||
appId: apiKeyAppId || authorizationAppid
|
appId: apiKeyAppId || authorizationAppid
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// root user
|
||||||
const parseRootKey = async (rootKey?: string, userId = '') => {
|
const parseRootKey = async (rootKey?: string, userId = '') => {
|
||||||
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
if (!rootKey || !process.env.ROOT_KEY || rootKey !== process.env.ROOT_KEY) {
|
||||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
@ -110,30 +113,31 @@ export const authUser = async ({
|
|||||||
let openApiKey = apikey;
|
let openApiKey = apikey;
|
||||||
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
|
let authType: `${AuthUserTypeEnum}` = AuthUserTypeEnum.token;
|
||||||
|
|
||||||
if (authToken) {
|
if (authToken && (cookie || token)) {
|
||||||
|
// user token(from fastgpt web)
|
||||||
uid = await authCookieToken(cookie, token);
|
uid = await authCookieToken(cookie, token);
|
||||||
authType = AuthUserTypeEnum.token;
|
authType = AuthUserTypeEnum.token;
|
||||||
} else if (authRoot) {
|
} else if (authRoot && rootkey) {
|
||||||
|
// root user
|
||||||
uid = await parseRootKey(rootkey, userid);
|
uid = await parseRootKey(rootkey, userid);
|
||||||
authType = AuthUserTypeEnum.root;
|
authType = AuthUserTypeEnum.root;
|
||||||
} else if (cookie || token) {
|
} else if (authApiKey && apikey) {
|
||||||
uid = await authCookieToken(cookie, token);
|
// apikey
|
||||||
authType = AuthUserTypeEnum.token;
|
|
||||||
} else if (apikey) {
|
|
||||||
const parseResult = await authOpenApiKey({ apikey });
|
const parseResult = await authOpenApiKey({ apikey });
|
||||||
uid = parseResult.userId;
|
uid = parseResult.userId;
|
||||||
authType = AuthUserTypeEnum.apikey;
|
authType = AuthUserTypeEnum.apikey;
|
||||||
openApiKey = parseResult.apikey;
|
openApiKey = parseResult.apikey;
|
||||||
} else if (authorization) {
|
} else if (authApiKey && authorization) {
|
||||||
|
// apikey from authorization
|
||||||
const authResponse = await parseAuthorization(authorization);
|
const authResponse = await parseAuthorization(authorization);
|
||||||
uid = authResponse.uid;
|
uid = authResponse.uid;
|
||||||
appId = authResponse.appId;
|
appId = authResponse.appId;
|
||||||
openApiKey = authResponse.apikey;
|
openApiKey = authResponse.apikey;
|
||||||
authType = AuthUserTypeEnum.apikey;
|
authType = AuthUserTypeEnum.apikey;
|
||||||
} else if (rootkey) {
|
}
|
||||||
uid = await parseRootKey(rootkey, userid);
|
|
||||||
authType = AuthUserTypeEnum.root;
|
// not rootUser and no uid, reject request
|
||||||
} else {
|
if (!rootkey && !uid) {
|
||||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,14 +162,12 @@ export const authApp = async ({
|
|||||||
appId,
|
appId,
|
||||||
userId,
|
userId,
|
||||||
authUser = true,
|
authUser = true,
|
||||||
authOwner = true,
|
authOwner = true
|
||||||
reserveDetail = false
|
|
||||||
}: {
|
}: {
|
||||||
appId: string;
|
appId: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
authUser?: boolean;
|
authUser?: boolean;
|
||||||
authOwner?: boolean;
|
authOwner?: boolean;
|
||||||
reserveDetail?: boolean; // focus reserve detail
|
|
||||||
}) => {
|
}) => {
|
||||||
// 获取 app 数据
|
// 获取 app 数据
|
||||||
const app = await App.findById<AppSchema>(appId);
|
const app = await App.findById<AppSchema>(appId);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ export type DatasetDataItemType = {
|
|||||||
source?: string;
|
source?: string;
|
||||||
file_id?: string;
|
file_id?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PgDataItemType = DatasetItemType & {
|
export type PgDataItemType = DatasetItemType & {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|||||||
1
projects/app/src/types/support/outLink.d.ts
vendored
1
projects/app/src/types/support/outLink.d.ts
vendored
@ -14,6 +14,7 @@ export interface OutLinkSchema {
|
|||||||
expiredTime?: Date;
|
expiredTime?: Date;
|
||||||
QPM: number;
|
QPM: number;
|
||||||
credit: number;
|
credit: number;
|
||||||
|
hookUrl?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user