mirror of
https://github.com/labring/FastGPT.git
synced 2025-12-25 20:02:47 +00:00
feat: add chat visibility controls and improve quote reader permissions (#6102)
* feat: add chat visibility controls and improve quote reader permissions * fix test * zod * fix * test & openapi * frontend filter * update name * fix * fix * rename variables * fix * test * fix build * fix * fix --------- Co-authored-by: archer <545436317@qq.com>
This commit is contained in:
parent
ab743b9358
commit
32affb0df0
|
|
@ -1,6 +1,8 @@
|
|||
import type { OpenAPIPath } from '../../type';
|
||||
import { AppLogPath } from './log';
|
||||
import { PublishChannelPath } from './publishChannel';
|
||||
|
||||
export const AppPath: OpenAPIPath = {
|
||||
...AppLogPath
|
||||
...AppLogPath,
|
||||
...PublishChannelPath
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import { PlaygroundPath } from './playground';
|
||||
|
||||
export const PublishChannelPath = {
|
||||
...PlaygroundPath
|
||||
};
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { z } from 'zod';
|
||||
import { ObjectIdSchema } from '../../../../../common/type/mongo';
|
||||
|
||||
// Playground Visibility Config Fields
|
||||
const PlaygroundVisibilityConfigFieldsSchema = z.object({
|
||||
showRunningStatus: z.boolean().meta({
|
||||
example: true,
|
||||
description: '是否显示运行状态'
|
||||
}),
|
||||
showCite: z.boolean().meta({
|
||||
example: true,
|
||||
description: '是否显示引用'
|
||||
}),
|
||||
showFullText: z.boolean().meta({
|
||||
example: true,
|
||||
description: '是否显示全文'
|
||||
}),
|
||||
canDownloadSource: z.boolean().meta({
|
||||
example: true,
|
||||
description: '是否可下载来源'
|
||||
})
|
||||
});
|
||||
|
||||
// Get Playground Visibility Config Parameters
|
||||
export const GetPlaygroundVisibilityConfigParamsSchema = z.object({
|
||||
appId: ObjectIdSchema.meta({
|
||||
example: '68ad85a7463006c963799a05',
|
||||
description: '应用 ID'
|
||||
})
|
||||
});
|
||||
export type GetPlaygroundVisibilityConfigParamsType = z.infer<
|
||||
typeof GetPlaygroundVisibilityConfigParamsSchema
|
||||
>;
|
||||
|
||||
// Playground Visibility Config Response
|
||||
export const PlaygroundVisibilityConfigResponseSchema = PlaygroundVisibilityConfigFieldsSchema;
|
||||
export type PlaygroundVisibilityConfigResponseType = z.infer<
|
||||
typeof PlaygroundVisibilityConfigResponseSchema
|
||||
>;
|
||||
|
||||
// Update Playground Visibility Config Parameters
|
||||
export const UpdatePlaygroundVisibilityConfigParamsSchema = z
|
||||
.object({
|
||||
appId: ObjectIdSchema.meta({
|
||||
example: '68ad85a7463006c963799a05',
|
||||
description: '应用 ID'
|
||||
})
|
||||
})
|
||||
.extend(PlaygroundVisibilityConfigFieldsSchema.shape);
|
||||
export type UpdatePlaygroundVisibilityConfigParamsType = z.infer<
|
||||
typeof UpdatePlaygroundVisibilityConfigParamsSchema
|
||||
>;
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
import { z } from 'zod';
|
||||
import type { OpenAPIPath } from '../../../../type';
|
||||
import {
|
||||
GetPlaygroundVisibilityConfigParamsSchema,
|
||||
PlaygroundVisibilityConfigResponseSchema,
|
||||
UpdatePlaygroundVisibilityConfigParamsSchema
|
||||
} from './api';
|
||||
import { TagsMap } from '../../../../tag';
|
||||
|
||||
export const PlaygroundPath: OpenAPIPath = {
|
||||
'/api/support/outLink/playground/config': {
|
||||
get: {
|
||||
summary: '获取门户配置',
|
||||
description:
|
||||
'获取指定应用的门户聊天界面的可见性配置,包括节点状态、响应详情、全文显示和原始来源显示的设置',
|
||||
tags: [TagsMap.publishChannel],
|
||||
requestParams: {
|
||||
query: GetPlaygroundVisibilityConfigParamsSchema
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: '成功返回门户配置',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: PlaygroundVisibilityConfigResponseSchema
|
||||
}
|
||||
}
|
||||
},
|
||||
400: {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
code: z.literal(500),
|
||||
statusText: z.literal('Invalid Params'),
|
||||
message: z.string(),
|
||||
data: z.null()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
401: {
|
||||
description: '用户未授权',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
code: z.literal(401),
|
||||
statusText: z.literal('unAuthorization'),
|
||||
message: z.string(),
|
||||
data: z.null()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'/api/support/outLink/playground/update': {
|
||||
post: {
|
||||
summary: '更新门户配置',
|
||||
description:
|
||||
'更新指定应用的门户聊天界面的可见性配置,包括节点状态、响应详情、全文显示和原始来源显示的设置。如果配置不存在则创建新配置',
|
||||
tags: [TagsMap.publishChannel],
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: UpdatePlaygroundVisibilityConfigParamsSchema
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: '成功更新门户配置',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.null()
|
||||
}
|
||||
}
|
||||
},
|
||||
400: {
|
||||
description: '请求参数错误',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
code: z.literal(500),
|
||||
statusText: z.literal('Invalid Params'),
|
||||
message: z.string(),
|
||||
data: z.null()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
401: {
|
||||
description: '用户未授权',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: z.object({
|
||||
code: z.literal(401),
|
||||
statusText: z.literal('unAuthorization'),
|
||||
message: z.string(),
|
||||
data: z.null()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -26,7 +26,7 @@ export const openAPIDocument = createDocument({
|
|||
'x-tagGroups': [
|
||||
{
|
||||
name: 'Agent 应用',
|
||||
tags: [TagsMap.appLog]
|
||||
tags: [TagsMap.appLog, TagsMap.publishChannel]
|
||||
},
|
||||
{
|
||||
name: '对话管理',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ export const TagsMap = {
|
|||
pluginToolTag: '工具标签',
|
||||
pluginTeam: '团队插件管理',
|
||||
|
||||
// Publish Channel
|
||||
publishChannel: '发布渠道',
|
||||
|
||||
/* Support */
|
||||
// Wallet
|
||||
walletBill: '订单',
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { z } from 'zod';
|
||||
import type { HistoryItemType } from '../../core/chat/type.d';
|
||||
import type { OutLinkSchema } from './type.d';
|
||||
import type { OutLinkSchema, PlaygroundVisibilityConfigType } from './type.d';
|
||||
import { PlaygroundVisibilityConfigSchema } from './type.d';
|
||||
|
||||
export type AuthOutLinkInitProps = {
|
||||
outLinkUid: string;
|
||||
|
|
@ -10,3 +12,20 @@ export type AuthOutLinkLimitProps = AuthOutLinkChatProps & { outLink: OutLinkSch
|
|||
export type AuthOutLinkResponse = {
|
||||
uid: string;
|
||||
};
|
||||
|
||||
export const UpdatePlaygroundVisibilityConfigBodySchema = PlaygroundVisibilityConfigSchema.extend({
|
||||
appId: z.string().min(1, 'App ID is required')
|
||||
});
|
||||
export type UpdatePlaygroundVisibilityConfigBody = z.infer<
|
||||
typeof UpdatePlaygroundVisibilityConfigBodySchema
|
||||
>;
|
||||
|
||||
export const PlaygroundVisibilityConfigQuerySchema = z.object({
|
||||
appId: z.string().min(1, 'App ID is required')
|
||||
});
|
||||
export type PlaygroundVisibilityConfigQuery = z.infer<typeof PlaygroundVisibilityConfigQuerySchema>;
|
||||
|
||||
export const PlaygroundVisibilityConfigResponseSchema = PlaygroundVisibilityConfigSchema;
|
||||
export type PlaygroundVisibilityConfigResponse = z.infer<
|
||||
typeof PlaygroundVisibilityConfigResponseSchema
|
||||
>;
|
||||
|
|
|
|||
|
|
@ -5,5 +5,6 @@ export enum PublishChannelEnum {
|
|||
feishu = 'feishu',
|
||||
dingtalk = 'dingtalk',
|
||||
wecom = 'wecom',
|
||||
officialAccount = 'official_account'
|
||||
officialAccount = 'official_account',
|
||||
playground = 'playground'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { z } from 'zod';
|
||||
import { AppSchema } from '../../core/app/type';
|
||||
import type { PublishChannelEnum } from './constant';
|
||||
import { RequireOnlyOne } from '../../common/type/utils';
|
||||
|
|
@ -63,14 +64,14 @@ export type OutLinkSchema<T extends OutlinkAppType = undefined> = {
|
|||
lastTime: Date;
|
||||
type: PublishChannelEnum;
|
||||
|
||||
// whether the response content is detailed
|
||||
responseDetail: boolean;
|
||||
// whether to hide the node status
|
||||
showNodeStatus?: boolean;
|
||||
// wheter to show the full text reader
|
||||
// showFullText?: boolean;
|
||||
// whether to show the complete quote
|
||||
showRawSource?: boolean;
|
||||
// whether to show the quote
|
||||
showCite: boolean;
|
||||
// whether to show the running status
|
||||
showRunningStatus: boolean;
|
||||
// whether to show the full text reader
|
||||
showFullText: boolean;
|
||||
// whether can download source
|
||||
canDownloadSource: boolean;
|
||||
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
|
|
@ -93,10 +94,10 @@ export type OutLinkSchema<T extends OutlinkAppType = undefined> = {
|
|||
export type OutLinkEditType<T = undefined> = {
|
||||
_id?: string;
|
||||
name: string;
|
||||
responseDetail?: OutLinkSchema<T>['responseDetail'];
|
||||
showNodeStatus?: OutLinkSchema<T>['showNodeStatus'];
|
||||
// showFullText?: OutLinkSchema<T>['showFullText'];
|
||||
showRawSource?: OutLinkSchema<T>['showRawSource'];
|
||||
showCite?: OutLinkSchema<T>['showCite'];
|
||||
showRunningStatus?: OutLinkSchema<T>['showRunningStatus'];
|
||||
showFullText?: OutLinkSchema<T>['showFullText'];
|
||||
canDownloadSource?: OutLinkSchema<T>['canDownloadSource'];
|
||||
// response when request
|
||||
immediateResponse?: string;
|
||||
// response when error or other situation
|
||||
|
|
@ -106,3 +107,12 @@ export type OutLinkEditType<T = undefined> = {
|
|||
// config for specific platform
|
||||
app?: T;
|
||||
};
|
||||
|
||||
export const PlaygroundVisibilityConfigSchema = z.object({
|
||||
showRunningStatus: z.boolean(),
|
||||
showCite: z.boolean(),
|
||||
showFullText: z.boolean(),
|
||||
canDownloadSource: z.boolean()
|
||||
});
|
||||
|
||||
export type PlaygroundVisibilityConfigType = z.infer<typeof PlaygroundVisibilityConfigSchema>;
|
||||
|
|
|
|||
|
|
@ -43,19 +43,21 @@ const OutLinkSchema = new Schema({
|
|||
type: Date
|
||||
},
|
||||
|
||||
responseDetail: {
|
||||
showRunningStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showNodeStatus: {
|
||||
showCite: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: false
|
||||
},
|
||||
// showFullText: {
|
||||
// type: Boolean
|
||||
// },
|
||||
showRawSource: {
|
||||
type: Boolean
|
||||
showFullText: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
canDownloadSource: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limit: {
|
||||
maxUsagePoints: {
|
||||
|
|
|
|||
|
|
@ -301,6 +301,8 @@
|
|||
"pro_modal_subtitle": "Join the business edition now to unlock more premium features",
|
||||
"pro_modal_title": "Business Edition Exclusive!",
|
||||
"pro_modal_unlock_button": "Unlock Now",
|
||||
"publish.chat_desc": "After logging into the portal, users can talk directly to the application",
|
||||
"publish.playground_link": "Redirect Link",
|
||||
"publish_channel": "Publish",
|
||||
"publish_success": "Publish Successful",
|
||||
"question_guide_tip": "After the conversation, 3 guiding questions will be generated for you.",
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@
|
|||
"core.ai.model.Vector Model": "Index model",
|
||||
"core.ai.model.doc_index_and_dialog": "Document Index & Dialog Index",
|
||||
"core.app.Api request": "API Request",
|
||||
"core.app.Api request desc": "Integrate into existing systems through API, or WeChat Work, Feishu, etc.",
|
||||
"core.app.Api request desc": "Connect to existing systems via API",
|
||||
"core.app.App intro": "App Introduction",
|
||||
"core.app.Auto execute": "Auto execute",
|
||||
"core.app.Chat Variable": "Chat Variable",
|
||||
|
|
@ -264,7 +264,7 @@
|
|||
"core.app.Set a name for your app": "Set a Name for Your App",
|
||||
"core.app.Setting ai property": "Click to Configure AI Model Properties",
|
||||
"core.app.Share link": "Login-Free Window",
|
||||
"core.app.Share link desc": "Share the link with other users, they can use it directly without logging in",
|
||||
"core.app.Share link desc": "Create shareable links and support login-free use",
|
||||
"core.app.Share link desc detail": "You can directly share this model with other users for conversation, they can use it directly without logging in. Note, this feature will consume your account balance, please keep the link safe!",
|
||||
"core.app.TTS": "Voice Playback",
|
||||
"core.app.TTS Tip": "After enabling, you can use the voice playback function after each conversation. Using this feature may incur additional costs.",
|
||||
|
|
@ -315,6 +315,7 @@
|
|||
"core.app.share.Amount limit tip": "Up to 10 groups",
|
||||
"core.app.share.Create link": "Create New Link",
|
||||
"core.app.share.Create link tip": "Creation successful. The share address has been copied and can be shared directly.",
|
||||
"core.app.share.Download source": "Download/open source original text",
|
||||
"core.app.share.Ip limit title": "IP Rate Limit (people/minute)",
|
||||
"core.app.share.Is response quote": "Return Quote",
|
||||
"core.app.share.Not share link": "No Share Link Created",
|
||||
|
|
@ -1102,9 +1103,11 @@
|
|||
"support.outlink.Max usage points tip": "The maximum number of points allowed for this link. It cannot be used after exceeding the limit. -1 means unlimited.",
|
||||
"support.outlink.Usage points": "Points Consumption",
|
||||
"support.outlink.share.Chat_quote_reader": "Full text reader",
|
||||
"support.outlink.share.Download source tips": "Download the original file of the knowledge base, or jump to the source website",
|
||||
"support.outlink.share.Full_text tips": "Allows reading of the complete dataset from which the referenced fragment is derived",
|
||||
"support.outlink.share.Response Quote": "Return Quote",
|
||||
"support.outlink.share.Response Quote": "View quoted snippets",
|
||||
"support.outlink.share.Response Quote tips": "Return quoted content in the share link, but do not allow users to download the original document",
|
||||
"support.outlink.share.Show full text tips": "View the complete file to which the quoted content belongs. You cannot view the original file or jump to the source website.",
|
||||
"support.outlink.share.running_node": "Running node",
|
||||
"support.outlink.share.show_complete_quote": "View original source",
|
||||
"support.outlink.share.show_complete_quote_tips": "View and download the complete citation document, or jump to the citation website",
|
||||
|
|
|
|||
|
|
@ -313,6 +313,8 @@
|
|||
"pro_modal_subtitle": "即刻加入商业版,解锁更多高级功能",
|
||||
"pro_modal_title": "商业版专享!",
|
||||
"pro_modal_unlock_button": "去解锁",
|
||||
"publish.chat_desc": "用户登录门户后可直接与应用对话",
|
||||
"publish.playground_link": "跳转链接",
|
||||
"publish_channel": "发布渠道",
|
||||
"publish_channel.wecom.empty": "发布到企业微信机器人,请先 <a>绑定自定义域名</a>,并且通过域名校验。",
|
||||
"publish_success": "发布成功",
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@
|
|||
"core.ai.model.Vector Model": "索引模型",
|
||||
"core.ai.model.doc_index_and_dialog": "文档索引 & 对话索引",
|
||||
"core.app.Api request": "API 访问",
|
||||
"core.app.Api request desc": "通过 API 接入到已有系统中,或企微、飞书等",
|
||||
"core.app.Api request desc": "通过 API 接入已有系统",
|
||||
"core.app.App intro": "应用介绍",
|
||||
"core.app.Auto execute": "自动执行",
|
||||
"core.app.Chat Variable": "对话框变量",
|
||||
|
|
@ -267,7 +267,7 @@
|
|||
"core.app.Set a name for your app": "给应用设置一个名称",
|
||||
"core.app.Setting ai property": "点击配置 AI 模型相关属性",
|
||||
"core.app.Share link": "免登录窗口",
|
||||
"core.app.Share link desc": "分享链接给其他用户,无需登录即可直接进行使用",
|
||||
"core.app.Share link desc": "创建可分享的链接,支持免登录使用",
|
||||
"core.app.Share link desc detail": "可以直接分享该模型给其他用户去进行对话,对方无需登录即可直接进行对话。注意,这个功能会消耗你账号的余额,请保管好链接!",
|
||||
"core.app.TTS": "语音播放",
|
||||
"core.app.TTS Tip": "开启后,每次对话后可使用语音播放功能。使用该功能可能产生额外费用。",
|
||||
|
|
@ -318,10 +318,12 @@
|
|||
"core.app.share.Amount limit tip": "最多创建 10 组",
|
||||
"core.app.share.Create link": "创建新链接",
|
||||
"core.app.share.Create link tip": "创建成功。已复制分享地址,可直接分享使用",
|
||||
"core.app.share.Download source": "下载/打开来源原文",
|
||||
"core.app.share.Ip limit title": "IP 限流(人/分钟)",
|
||||
"core.app.share.Is response quote": "返回引用",
|
||||
"core.app.share.Not share link": "没有创建分享链接",
|
||||
"core.app.share.Role check": "身份校验",
|
||||
"core.app.share.Show full text": "查看引用全文",
|
||||
"core.app.switch_to_template_market": "跳转模板市场",
|
||||
"core.app.tip.Add a intro to app": "快来给应用一个介绍~",
|
||||
"core.app.tip.chatNodeSystemPromptTip": "在此输入提示词",
|
||||
|
|
@ -1110,9 +1112,11 @@
|
|||
"support.outlink.Max usage points tip": "该链接最多允许使用多少积分,超出后将无法使用。-1 代表无限制。",
|
||||
"support.outlink.Usage points": "积分消耗",
|
||||
"support.outlink.share.Chat_quote_reader": "全文阅读器",
|
||||
"support.outlink.share.Download source tips": "下载知识库原文件,或跳转来源网站",
|
||||
"support.outlink.share.Full_text tips": "允许阅读该引用片段来源的完整数据集",
|
||||
"support.outlink.share.Response Quote": "引用内容",
|
||||
"support.outlink.share.Response Quote": "查看引用片段",
|
||||
"support.outlink.share.Response Quote tips": "查看知识库搜索的引用内容,不可查看完整引用文档或跳转引用网站",
|
||||
"support.outlink.share.Show full text tips": "查看引用内容所属的完整文件,不可查看原文件或跳转来来源网站",
|
||||
"support.outlink.share.running_node": "运行节点",
|
||||
"support.outlink.share.show_complete_quote": "查看来源原文",
|
||||
"support.outlink.share.show_complete_quote_tips": "查看及下载完整引用文档,或跳转引用网站",
|
||||
|
|
|
|||
|
|
@ -299,6 +299,8 @@
|
|||
"pro_modal_subtitle": "即刻加入商業版,解鎖更多高級功能",
|
||||
"pro_modal_title": "商業版專享!",
|
||||
"pro_modal_unlock_button": "去解鎖",
|
||||
"publish.chat_desc": "用戶登錄門戶後可直接與應用對話",
|
||||
"publish.playground_link": "跳轉鏈接",
|
||||
"publish_channel": "發布通道",
|
||||
"publish_success": "發布成功",
|
||||
"question_guide_tip": "對話結束後,會為你產生 3 個引導性問題。",
|
||||
|
|
|
|||
|
|
@ -234,7 +234,7 @@
|
|||
"core.ai.model.Vector Model": "索引模型",
|
||||
"core.ai.model.doc_index_and_dialog": "文件索引與對話索引",
|
||||
"core.app.Api request": "API 存取",
|
||||
"core.app.Api request desc": "透過 API 整合到現有系統中,或整合到企業微信、飛書等",
|
||||
"core.app.Api request desc": "通過 API 接入已有系統",
|
||||
"core.app.App intro": "應用程式介紹",
|
||||
"core.app.Auto execute": "自動執行",
|
||||
"core.app.Chat Variable": "對話變數",
|
||||
|
|
@ -264,7 +264,7 @@
|
|||
"core.app.Set a name for your app": "為您的應用程式命名",
|
||||
"core.app.Setting ai property": "點選設定 AI 模型相關屬性",
|
||||
"core.app.Share link": "免登入視窗",
|
||||
"core.app.Share link desc": "分享連結給其他使用者,無需登入即可直接使用",
|
||||
"core.app.Share link desc": "創建可分享的鏈接,支持免登錄使用",
|
||||
"core.app.Share link desc detail": "您可以直接分享此模型給其他使用者進行對話,對方無需登入即可直接使用。請注意,此功能會消耗您帳戶的餘額,請妥善保管連結!",
|
||||
"core.app.TTS": "語音播放",
|
||||
"core.app.TTS Tip": "開啟後,每次對話後可使用語音播放功能。使用此功能可能會產生額外費用。",
|
||||
|
|
@ -315,6 +315,7 @@
|
|||
"core.app.share.Amount limit tip": "最多 10 組",
|
||||
"core.app.share.Create link": "建立新連結",
|
||||
"core.app.share.Create link tip": "建立成功。已複製分享網址,可直接分享使用",
|
||||
"core.app.share.Download source": "下載/打開來源原文",
|
||||
"core.app.share.Ip limit title": "IP 限流(人/分鐘)",
|
||||
"core.app.share.Is response quote": "返回引用",
|
||||
"core.app.share.Not share link": "尚未建立分享連結",
|
||||
|
|
@ -1099,9 +1100,11 @@
|
|||
"support.outlink.Max usage points tip": "此連結最多允許使用多少點數,超出後將無法使用。-1 代表無限制。",
|
||||
"support.outlink.Usage points": "點數消耗",
|
||||
"support.outlink.share.Chat_quote_reader": "全文閱讀器",
|
||||
"support.outlink.share.Download source tips": "下載知識庫原文件,或跳轉來源網站",
|
||||
"support.outlink.share.Full_text tips": "允許閱讀該引用片段來源的完整資料集",
|
||||
"support.outlink.share.Response Quote": "回傳引用",
|
||||
"support.outlink.share.Response Quote": "查看引用片段",
|
||||
"support.outlink.share.Response Quote tips": "在分享連結中回傳引用內容,但不允許使用者下載原始文件",
|
||||
"support.outlink.share.Show full text tips": "查看引用內容所屬的完整文件,不可查看原文件或跳轉來來源網站",
|
||||
"support.outlink.share.running_node": "執行節點",
|
||||
"support.outlink.share.show_complete_quote": "檢視原始內容",
|
||||
"support.outlink.share.show_complete_quote_tips": "檢視及下載完整引用文件,或跳轉至引用網站",
|
||||
|
|
|
|||
|
|
@ -163,12 +163,12 @@ const ChatItem = (props: Props) => {
|
|||
|
||||
const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting);
|
||||
const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType);
|
||||
const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus);
|
||||
const showRunningStatus = useContextSelector(ChatItemContext, (v) => v.showRunningStatus);
|
||||
|
||||
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData);
|
||||
const isShowReadRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const isShowFullText = useContextSelector(ChatItemContext, (v) => v.isShowFullText);
|
||||
|
||||
const { totalQuoteList: quoteList = [] } = useMemo(
|
||||
() => addStatisticalDataToHistoryItem(chat),
|
||||
|
|
@ -258,7 +258,7 @@ const ChatItem = (props: Props) => {
|
|||
setCiteModalData({
|
||||
rawSearch: quoteList,
|
||||
metadata:
|
||||
item?.collectionId && isShowReadRawSource
|
||||
item?.collectionId && isShowFullText
|
||||
? {
|
||||
appId: appId,
|
||||
chatId: chatId,
|
||||
|
|
@ -322,7 +322,7 @@ const ChatItem = (props: Props) => {
|
|||
<ChatAvatar src={avatar} type={type} />
|
||||
|
||||
{/* Workflow status */}
|
||||
{!!chatStatusMap && statusBoxData && isLastChild && showNodeStatus && (
|
||||
{!!chatStatusMap && statusBoxData && isLastChild && showRunningStatus && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
px={3}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const QuoteList = React.memo(function QuoteList({
|
|||
chatId: v.chatId,
|
||||
...(v.outLinkAuthData || {})
|
||||
}));
|
||||
const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource);
|
||||
const canDownloadSource = useContextSelector(ChatItemContext, (v) => v.canDownloadSource);
|
||||
const showRouteToDatasetDetail = useContextSelector(
|
||||
ChatItemContext,
|
||||
(v) => v.showRouteToDatasetDetail
|
||||
|
|
@ -87,7 +87,7 @@ const QuoteList = React.memo(function QuoteList({
|
|||
>
|
||||
<QuoteItem
|
||||
quoteItem={item}
|
||||
canViewSource={showRawSource}
|
||||
canDownloadSource={canDownloadSource}
|
||||
canEditData={showRouteToDatasetDetail}
|
||||
canEditDataset={showRouteToDatasetDetail}
|
||||
{...RawSourceBoxProps}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { addStatisticalDataToHistoryItem } from '@/global/core/chat/utils';
|
|||
import { useSize } from 'ahooks';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatBoxContext } from '../Provider';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
export type CitationRenderItem = {
|
||||
type: 'dataset' | 'link';
|
||||
|
|
@ -48,12 +49,22 @@ const ResponseTags = ({
|
|||
|
||||
const chatTime = historyItem.time || new Date();
|
||||
const durationSeconds = historyItem.durationSeconds || 0;
|
||||
const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite);
|
||||
const {
|
||||
totalQuoteList: quoteList = [],
|
||||
llmModuleAccount = 0,
|
||||
historyPreviewLength = 0,
|
||||
toolCiteLinks = []
|
||||
} = useMemo(() => addStatisticalDataToHistoryItem(historyItem), [historyItem]);
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
...addStatisticalDataToHistoryItem(historyItem),
|
||||
...(isShowCite
|
||||
? {
|
||||
totalQuoteList: []
|
||||
}
|
||||
: {})
|
||||
};
|
||||
}, [historyItem, isShowCite]);
|
||||
|
||||
const [quoteFolded, setQuoteFolded] = useState<boolean>(true);
|
||||
|
||||
|
|
@ -78,6 +89,7 @@ const ResponseTags = ({
|
|||
: true;
|
||||
|
||||
const citationRenderList: CitationRenderItem[] = useMemo(() => {
|
||||
if (!isShowCite) return [];
|
||||
// Dataset citations
|
||||
const datasetItems = Object.values(
|
||||
quoteList.reduce((acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
|
|
@ -116,7 +128,7 @@ const ResponseTags = ({
|
|||
}));
|
||||
|
||||
return [...datasetItems, ...linkItems];
|
||||
}, [quoteList, toolCiteLinks, onOpenCiteModal]);
|
||||
}, [quoteList, toolCiteLinks, onOpenCiteModal, isShowCite]);
|
||||
|
||||
const notEmptyTags = notSharePage || quoteList.length > 0 || (isPc && durationSeconds > 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,13 @@ import { eventBus, EventNameEnum } from '@/web/common/utils/eventbus';
|
|||
import { SelectOptionsComponent, FormInputComponent } from './Interactive/InteractiveComponents';
|
||||
import { extractDeepestInteractive } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { type OnOpenCiteModalProps } from '@/web/core/chat/context/chatItemContext';
|
||||
import {
|
||||
type OnOpenCiteModalProps,
|
||||
ChatItemContext
|
||||
} from '@/web/core/chat/context/chatItemContext';
|
||||
import { WorkflowRuntimeContext } from '../ChatContainer/context/workflowRuntimeContext';
|
||||
import { useCreation } from 'ahooks';
|
||||
import { removeDatasetCiteText } from '@fastgpt/global/core/ai/llm/utils';
|
||||
|
||||
const accordionButtonStyle = {
|
||||
w: 'auto',
|
||||
|
|
@ -102,13 +106,16 @@ const RenderText = React.memo(function RenderText({
|
|||
const appId = useContextSelector(WorkflowRuntimeContext, (v) => v.appId);
|
||||
const chatId = useContextSelector(WorkflowRuntimeContext, (v) => v.chatId);
|
||||
const outLinkAuthData = useContextSelector(WorkflowRuntimeContext, (v) => v.outLinkAuthData);
|
||||
const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite);
|
||||
|
||||
const source = useMemo(() => {
|
||||
if (!text) return '';
|
||||
|
||||
// Remove quote references if not showing response detail
|
||||
return text;
|
||||
}, [text]);
|
||||
if (isShowCite) {
|
||||
return text;
|
||||
}
|
||||
return removeDatasetCiteText(text, isShowCite);
|
||||
}, [text, isShowCite]);
|
||||
|
||||
const chatAuthData = useCreation(() => {
|
||||
return { appId, chatId, chatItemDataId, ...outLinkAuthData };
|
||||
|
|
@ -329,6 +336,8 @@ const AIResponseBox = ({
|
|||
isChatting: boolean;
|
||||
onOpenCiteModal?: (e?: OnOpenCiteModalProps) => void;
|
||||
}) => {
|
||||
const showRunningStatus = useContextSelector(ChatItemContext, (v) => v.showRunningStatus);
|
||||
|
||||
if (value.type === ChatItemValueTypeEnum.text && value.text) {
|
||||
return (
|
||||
<RenderText
|
||||
|
|
@ -348,7 +357,7 @@ const AIResponseBox = ({
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools) {
|
||||
if (value.type === ChatItemValueTypeEnum.tool && value.tools && showRunningStatus) {
|
||||
return <RenderTool showAnimation={isChatting} tools={value.tools} />;
|
||||
}
|
||||
if (value.type === ChatItemValueTypeEnum.interactive && value.interactive) {
|
||||
|
|
|
|||
|
|
@ -91,13 +91,13 @@ export const formatScore = (score: ScoreItemType[]) => {
|
|||
|
||||
const QuoteItem = ({
|
||||
quoteItem,
|
||||
canViewSource,
|
||||
canDownloadSource,
|
||||
canEditData,
|
||||
canEditDataset,
|
||||
...RawSourceBoxProps
|
||||
}: {
|
||||
quoteItem: SearchDataResponseItemType;
|
||||
canViewSource?: boolean;
|
||||
canDownloadSource?: boolean;
|
||||
canEditData?: boolean;
|
||||
canEditDataset?: boolean;
|
||||
} & Omit<readCollectionSourceBody, 'collectionId'>) => {
|
||||
|
|
@ -208,7 +208,7 @@ const QuoteItem = ({
|
|||
collectionId={quoteItem.collectionId}
|
||||
sourceName={quoteItem.sourceName}
|
||||
sourceId={quoteItem.sourceId}
|
||||
canView={canViewSource}
|
||||
canView={canDownloadSource}
|
||||
{...RawSourceBoxProps}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
|
|
|
|||
|
|
@ -205,10 +205,10 @@ const Render = ({
|
|||
return (
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
canDownloadSource={true}
|
||||
isShowCite={true}
|
||||
isShowFullText={true}
|
||||
showRunningStatus={true}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest
|
||||
|
|
|
|||
|
|
@ -264,10 +264,10 @@ const Render = (props: Props) => {
|
|||
return (
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
canDownloadSource={true}
|
||||
isShowCite={true}
|
||||
isShowFullText={true}
|
||||
showRunningStatus={true}
|
||||
>
|
||||
<ChatRecordContextProvider params={params} feedbackRecordId={feedbackRecordId}>
|
||||
<DetailLogsModal
|
||||
|
|
|
|||
|
|
@ -182,10 +182,10 @@ const Render = ({
|
|||
return (
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
canDownloadSource={true}
|
||||
isShowCite={true}
|
||||
isShowFullText={true}
|
||||
showRunningStatus={true}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest currentTool={currentTool} url={url} headerSecret={headerSecret} />
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ const DingTalk = ({ appId }: { appId: string }) => {
|
|||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
showCite: item.showCite,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
});
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ const FeiShu = ({ appId }: { appId: string }) => {
|
|||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
showCite: item.showCite,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
});
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
|||
}`
|
||||
: ''}
|
||||
</Td>
|
||||
<Td>{item.responseDetail ? '✔' : '✖'}</Td>
|
||||
<Td>{item.showCite ? '✔' : '✖'}</Td>
|
||||
{feConfigs?.isPlus && (
|
||||
<>
|
||||
<Td>{item?.limit?.QPM || '-'}</Td>
|
||||
|
|
@ -182,10 +182,10 @@ const Share = ({ appId }: { appId: string; type: PublishChannelEnum }) => {
|
|||
setEditLinkData({
|
||||
_id: item._id,
|
||||
name: item.name,
|
||||
responseDetail: item.responseDetail ?? false,
|
||||
showRawSource: item.showRawSource ?? false,
|
||||
// showFullText: item.showFullText ?? false,
|
||||
showNodeStatus: item.showNodeStatus ?? false,
|
||||
showCite: item.showCite,
|
||||
canDownloadSource: item.canDownloadSource,
|
||||
showFullText: item.showFullText,
|
||||
showRunningStatus: item.showRunningStatus,
|
||||
limit: item.limit
|
||||
})
|
||||
},
|
||||
|
|
@ -281,9 +281,9 @@ function EditLinkModal({
|
|||
defaultValues: defaultData
|
||||
});
|
||||
|
||||
const responseDetail = watch('responseDetail');
|
||||
// const showFullText = watch('showFullText');
|
||||
const showRawSource = watch('showRawSource');
|
||||
const showCite = watch('showCite');
|
||||
const showFullText = watch('showFullText');
|
||||
const canDownloadSource = watch('canDownloadSource');
|
||||
|
||||
const isEdit = useMemo(() => !!defaultData._id, [defaultData]);
|
||||
|
||||
|
|
@ -413,7 +413,7 @@ function EditLinkModal({
|
|||
</Box>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<FormLabel>{t('publish:show_node')}</FormLabel>
|
||||
<Switch {...register('showNodeStatus')} />
|
||||
<Switch {...register('showRunningStatus')} />
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
|
|
@ -424,56 +424,56 @@ function EditLinkModal({
|
|||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('responseDetail', {
|
||||
{...register('showCite', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
// setValue('showFullText', false);
|
||||
setValue('showRawSource', false);
|
||||
setValue('showFullText', false);
|
||||
setValue('canDownloadSource', false);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={responseDetail}
|
||||
isChecked={showCite}
|
||||
/>
|
||||
</Flex>
|
||||
{/* <Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.Chat_quote_reader')}</FormLabel>
|
||||
<FormLabel>{t('common:core.app.share.Show full text')}</FormLabel>
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.outlink.share.Full_text tips')}
|
||||
label={t('common:support.outlink.share.Show full text tips')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('showFullText', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
if (!e.target.checked) {
|
||||
setValue('canDownloadSource', false);
|
||||
} else {
|
||||
setValue('showRawSource', false);
|
||||
setValue('showCite', true);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={showFullText}
|
||||
/>
|
||||
</Flex> */}
|
||||
</Flex>
|
||||
<Flex alignItems={'center'} mt={4} justify={'space-between'} height={'36px'}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel>{t('common:support.outlink.share.show_complete_quote')}</FormLabel>
|
||||
<FormLabel>{t('common:core.app.share.Download source')}</FormLabel>
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.outlink.share.show_complete_quote_tips')}
|
||||
label={t('common:support.outlink.share.Download source tips')}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('showRawSource', {
|
||||
{...register('canDownloadSource', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('responseDetail', true);
|
||||
// setValue('showFullText', true);
|
||||
setValue('showFullText', true);
|
||||
setValue('showCite', true);
|
||||
}
|
||||
}
|
||||
})}
|
||||
isChecked={showRawSource}
|
||||
isChecked={canDownloadSource}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ const OffiAccount = ({ appId }: { appId: string }) => {
|
|||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
showCite: item.showCite,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, Grid, Switch } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||
import {
|
||||
getPlaygroundVisibilityConfig,
|
||||
updatePlaygroundVisibilityConfig
|
||||
} from '@/web/support/outLink/api';
|
||||
import type { PlaygroundVisibilityConfigType } from '@fastgpt/global/support/outLink/type';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
|
||||
import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants';
|
||||
|
||||
const defaultPlaygroundVisibilityForm: PlaygroundVisibilityConfigType = {
|
||||
showRunningStatus: true,
|
||||
showCite: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true
|
||||
};
|
||||
|
||||
const PlaygroundVisibilityConfig = ({ appId }: { appId: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { copyData } = useCopyData();
|
||||
|
||||
const { register, watch, setValue, reset } = useForm({
|
||||
defaultValues: defaultPlaygroundVisibilityForm
|
||||
});
|
||||
|
||||
const showCite = watch('showCite');
|
||||
const showFullText = watch('showFullText');
|
||||
const canDownloadSource = watch('canDownloadSource');
|
||||
|
||||
const playgroundLink = useMemo(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return `${window.location.origin}/chat?appId=${appId}&pane=${ChatSidebarPaneEnum.RECENTLY_USED_APPS}`;
|
||||
}
|
||||
return '';
|
||||
}, [appId]);
|
||||
|
||||
useRequest2(() => getPlaygroundVisibilityConfig({ appId }), {
|
||||
onSuccess: (data) => {
|
||||
reset({
|
||||
showRunningStatus: data.showRunningStatus,
|
||||
showCite: data.showCite,
|
||||
showFullText: data.showFullText,
|
||||
canDownloadSource: data.canDownloadSource
|
||||
});
|
||||
},
|
||||
manual: false
|
||||
});
|
||||
|
||||
const { runAsync: saveConfig } = useRequest2(
|
||||
async (data: PlaygroundVisibilityConfigType) => {
|
||||
return await updatePlaygroundVisibilityConfig({
|
||||
appId,
|
||||
...data
|
||||
});
|
||||
},
|
||||
{
|
||||
successToast: t('common:save_success')
|
||||
}
|
||||
);
|
||||
|
||||
const autoSave = async () => {
|
||||
const values = watch();
|
||||
await saveConfig(values);
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex flexDirection="column" h="100%">
|
||||
<Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'} mb={3}>
|
||||
{t('app:publish.playground_link')}
|
||||
</Box>
|
||||
|
||||
<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'} mb={6}>
|
||||
<Flex
|
||||
p={3}
|
||||
bg={'myWhite.500'}
|
||||
border="base"
|
||||
borderTopLeftRadius={'md'}
|
||||
borderTopRightRadius={'md'}
|
||||
alignItems={'center'}
|
||||
>
|
||||
<Box flex={1} fontSize={'xs'} color={'myGray.600'}>
|
||||
{t('common:core.app.outLink.Link block title')}
|
||||
</Box>
|
||||
<MyIcon
|
||||
name={'copy'}
|
||||
w={'16px'}
|
||||
color={'myGray.600'}
|
||||
cursor={'pointer'}
|
||||
_hover={{ color: 'primary.500' }}
|
||||
onClick={() => copyData(playgroundLink)}
|
||||
/>
|
||||
</Flex>
|
||||
<Box whiteSpace={'nowrap'} p={3} overflowX={'auto'}>
|
||||
{playgroundLink}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box fontSize={'sm'} fontWeight={'medium'} color={'myGray.900'}>
|
||||
{t('publish:private_config')}
|
||||
</Box>
|
||||
|
||||
<Grid templateColumns={'1fr 1fr'} gap={4} mt={4}>
|
||||
<Flex alignItems={'center'}>
|
||||
<FormLabel fontSize={'12px'} flex={'0 0 127px'}>
|
||||
{t('publish:show_node')}
|
||||
</FormLabel>
|
||||
<Switch
|
||||
{...register('showRunningStatus', {
|
||||
onChange: autoSave
|
||||
})}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex alignItems={'center'} flex={'0 0 158px'}>
|
||||
<FormLabel fontSize={'12px'}>
|
||||
{t('common:support.outlink.share.Response Quote')}
|
||||
</FormLabel>
|
||||
<QuestionTip ml={1} label={t('common:support.outlink.share.Response Quote tips')} />
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('showCite', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
setValue('showFullText', false);
|
||||
setValue('canDownloadSource', false);
|
||||
}
|
||||
autoSave();
|
||||
}
|
||||
})}
|
||||
isChecked={showCite}
|
||||
/>
|
||||
</Flex>
|
||||
</Grid>
|
||||
|
||||
<Grid templateColumns={'1fr 1fr'} gap={4} mt={4}>
|
||||
<Flex>
|
||||
<Flex alignItems={'center'} flex={'0 0 127px'}>
|
||||
<FormLabel fontSize={'12px'}>{t('common:core.app.share.Show full text')}</FormLabel>
|
||||
<QuestionTip ml={1} label={t('common:support.outlink.share.Show full text tips')} />
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('showFullText', {
|
||||
onChange(e) {
|
||||
if (!e.target.checked) {
|
||||
setValue('canDownloadSource', false);
|
||||
} else {
|
||||
setValue('showCite', true);
|
||||
}
|
||||
autoSave();
|
||||
}
|
||||
})}
|
||||
isChecked={showFullText}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Flex alignItems={'center'} flex={'0 0 158px'}>
|
||||
<FormLabel fontSize={'12px'} fontWeight={'medium'}>
|
||||
{t('common:core.app.share.Download source')}
|
||||
</FormLabel>
|
||||
<QuestionTip ml={1} label={t('common:support.outlink.share.Download source tips')} />
|
||||
</Flex>
|
||||
<Switch
|
||||
{...register('canDownloadSource', {
|
||||
onChange(e) {
|
||||
if (e.target.checked) {
|
||||
setValue('showFullText', true);
|
||||
setValue('showCite', true);
|
||||
}
|
||||
autoSave();
|
||||
}
|
||||
})}
|
||||
isChecked={canDownloadSource}
|
||||
/>
|
||||
</Flex>
|
||||
</Grid>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaygroundVisibilityConfig;
|
||||
|
|
@ -200,7 +200,7 @@ const Wecom = ({ appId }: { appId: string }) => {
|
|||
name: item.name,
|
||||
limit: item.limit,
|
||||
app: item.app,
|
||||
responseDetail: item.responseDetail,
|
||||
showCite: item.showCite,
|
||||
defaultResponse: item.defaultResponse,
|
||||
immediateResponse: item.immediateResponse
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ const FeiShu = dynamic(() => import('./FeiShu'));
|
|||
const DingTalk = dynamic(() => import('./DingTalk'));
|
||||
const Wecom = dynamic(() => import('./Wecom'));
|
||||
const OffiAccount = dynamic(() => import('./OffiAccount'));
|
||||
const Playground = dynamic(() => import('./Playground'));
|
||||
|
||||
const OutLink = () => {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -85,7 +86,14 @@ const OutLink = () => {
|
|||
isProFn: true
|
||||
}
|
||||
]
|
||||
: [])
|
||||
: []),
|
||||
{
|
||||
icon: 'core/chat/sidebar/home',
|
||||
title: t('common:navbar.Chat'),
|
||||
desc: t('app:publish.chat_desc'),
|
||||
value: PublishChannelEnum.playground,
|
||||
isProFn: false
|
||||
}
|
||||
]);
|
||||
|
||||
const [linkType, setLinkType] = useState<PublishChannelEnum>(PublishChannelEnum.share);
|
||||
|
|
@ -141,6 +149,7 @@ const OutLink = () => {
|
|||
{linkType === PublishChannelEnum.dingtalk && <DingTalk appId={appId} />}
|
||||
{linkType === PublishChannelEnum.wecom && <Wecom appId={appId} />}
|
||||
{linkType === PublishChannelEnum.officialAccount && <OffiAccount appId={appId} />}
|
||||
{linkType === PublishChannelEnum.playground && <Playground appId={appId} />}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -118,10 +118,10 @@ const Render = ({ appForm, setRenderEdit }: Props) => {
|
|||
return (
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
canDownloadSource={true}
|
||||
isShowCite={true}
|
||||
isShowFullText={true}
|
||||
showRunningStatus={true}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest appForm={appForm} setRenderEdit={setRenderEdit} />
|
||||
|
|
|
|||
|
|
@ -206,10 +206,10 @@ const Render = (Props: Props) => {
|
|||
return (
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={true}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
// isShowFullText={true}
|
||||
showNodeStatus
|
||||
canDownloadSource={true}
|
||||
isShowCite={true}
|
||||
isShowFullText={true}
|
||||
showRunningStatus={true}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<ChatTest {...Props} chatId={chatId} />
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import { getCollectionQuote } from '@/web/core/chat/api';
|
|||
import MyIconButton from '@fastgpt/web/components/common/Icon/button';
|
||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||
import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { ChatItemContext } from '@/web/core/chat/context/chatItemContext';
|
||||
|
||||
const CollectionReader = ({
|
||||
rawSearch,
|
||||
|
|
@ -37,6 +39,8 @@ const CollectionReader = ({
|
|||
const router = useRouter();
|
||||
const { userInfo } = useUserStore();
|
||||
|
||||
const canDownloadSource = useContextSelector(ChatItemContext, (v) => v.canDownloadSource);
|
||||
|
||||
const { collectionId, datasetId, chatItemDataId, sourceId, sourceName, quoteId } = metadata;
|
||||
const [quoteIndex, setQuoteIndex] = useState(0);
|
||||
|
||||
|
|
@ -175,11 +179,13 @@ const CollectionReader = ({
|
|||
{sourceName || t('common:unknow_source')}
|
||||
</Box>
|
||||
<Box ml={3}>
|
||||
<DownloadButton
|
||||
canAccessRawData={true}
|
||||
onDownload={handleDownload}
|
||||
onRead={handleRead}
|
||||
/>
|
||||
{canDownloadSource && (
|
||||
<DownloadButton
|
||||
canAccessRawData={true}
|
||||
onDownload={handleDownload}
|
||||
onRead={handleRead}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
<MyIconButton
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const AppChatWindow = ({ myApps }: Props) => {
|
|||
const onUpdateHistoryTitle = useContextSelector(ChatContext, (v) => v.onUpdateHistoryTitle);
|
||||
|
||||
const isPlugin = useContextSelector(ChatItemContext, (v) => v.isPlugin);
|
||||
const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite);
|
||||
const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId);
|
||||
const chatBoxData = useContextSelector(ChatItemContext, (v) => v.chatBoxData);
|
||||
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
||||
|
|
@ -106,7 +107,8 @@ const AppChatWindow = ({ myApps }: Props) => {
|
|||
variables,
|
||||
responseChatItemId,
|
||||
appId,
|
||||
chatId
|
||||
chatId,
|
||||
retainDatasetCite: isShowCite
|
||||
},
|
||||
abortCtrl: controller,
|
||||
onMessage: generatingMessage
|
||||
|
|
@ -122,7 +124,7 @@ const AppChatWindow = ({ myApps }: Props) => {
|
|||
|
||||
return { responseText, isNewChat: forbidLoadChat.current };
|
||||
},
|
||||
[appId, chatId, onUpdateHistoryTitle, setChatBoxData, forbidLoadChat]
|
||||
[appId, chatId, onUpdateHistoryTitle, setChatBoxData, forbidLoadChat, isShowCite]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ const HomeChatWindow = ({ myApps }: Props) => {
|
|||
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
||||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const resetVariables = useContextSelector(ChatItemContext, (v) => v.resetVariables);
|
||||
const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite);
|
||||
|
||||
const pane = useContextSelector(ChatSettingContext, (v) => v.pane);
|
||||
const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings);
|
||||
|
|
@ -216,7 +217,8 @@ const HomeChatWindow = ({ myApps }: Props) => {
|
|||
variables,
|
||||
responseChatItemId,
|
||||
appId,
|
||||
chatId
|
||||
chatId,
|
||||
retainDatasetCite: isShowCite
|
||||
},
|
||||
abortCtrl: controller,
|
||||
onMessage: generatingMessage
|
||||
|
|
@ -264,6 +266,7 @@ const HomeChatWindow = ({ myApps }: Props) => {
|
|||
appId,
|
||||
appName: t('chat:home.chat_app'),
|
||||
chatId,
|
||||
retainDatasetCite: isShowCite,
|
||||
...form2AppWorkflow(formData, t)
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
|
|
@ -394,7 +397,8 @@ const HomeChatWindow = ({ myApps }: Props) => {
|
|||
setSelectedToolIds,
|
||||
setChatBoxData,
|
||||
isPc,
|
||||
isQuickApp
|
||||
isQuickApp,
|
||||
isShowCite
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@ const TestResults = React.memo(function TestResults({
|
|||
<Box mt={1} gap={4}>
|
||||
{datasetTestItem?.results.map((item, index) => (
|
||||
<Box key={item.id} p={3} borderRadius={'lg'} bg={'myGray.100'} _notLast={{ mb: 2 }}>
|
||||
<QuoteItem quoteItem={item} canViewSource canEditData />
|
||||
<QuoteItem quoteItem={item} canDownloadSource canEditData />
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -5,36 +5,56 @@ import type { S3MQJobData } from '@fastgpt/service/common/s3/mq';
|
|||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { connectionMongo } from '@fastgpt/service/common/mongo';
|
||||
|
||||
export type ResponseType = {
|
||||
message: string;
|
||||
retriedCount: number;
|
||||
failedCount: number;
|
||||
shareLinkMigration: {
|
||||
totalRecords: number;
|
||||
updatedRecords: number;
|
||||
updateResults: Array<{
|
||||
operation: string;
|
||||
updated: number;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps,
|
||||
res: ApiResponseType<ResponseType>
|
||||
): Promise<ResponseType> {
|
||||
await authCert({ req, authRoot: true });
|
||||
const queue = getQueue<S3MQJobData>(QueueNames.s3FileDelete);
|
||||
/**
|
||||
* 4.14.5 版本数据初始化脚本
|
||||
* 1. 重试所有失败的 S3 删除任务
|
||||
* 2. 为所有 share 类型的 OutLink 记录添加 showFullText 字段
|
||||
* 3. 重命名字段:
|
||||
* - showNodeStatus -> showRunningStatus
|
||||
* - responseDetail -> showCite
|
||||
* - showRawSource -> canDownloadSource
|
||||
*/
|
||||
|
||||
// Get all failed jobs and retry them
|
||||
/**
|
||||
* 功能1: 重试所有失败的 S3 删除任务
|
||||
*/
|
||||
async function retryFailedS3DeleteJobs(): Promise<{
|
||||
retriedCount: number;
|
||||
failedCount: number;
|
||||
}> {
|
||||
const queue = getQueue<S3MQJobData>(QueueNames.s3FileDelete);
|
||||
const failedJobs = await queue.getFailed();
|
||||
console.log(`Found ${failedJobs.length} failed jobs`);
|
||||
console.log(`Found ${failedJobs.length} failed S3 delete jobs`);
|
||||
|
||||
let retriedCount = 0;
|
||||
|
||||
await batchRun(
|
||||
failedJobs,
|
||||
async (job) => {
|
||||
addLog.debug(`Retrying job with 3 new attempts`, { retriedCount });
|
||||
addLog.debug(`Retrying S3 delete job with new attempts`, { retriedCount });
|
||||
try {
|
||||
// Remove old job and recreate with new attempts
|
||||
const jobData = job.data;
|
||||
await job.remove();
|
||||
|
||||
// Add new job with 3 more attempts
|
||||
// Add new job with more attempts
|
||||
await queue.add('delete-s3-files', jobData, {
|
||||
attempts: 10,
|
||||
removeOnFail: {
|
||||
|
|
@ -49,19 +69,217 @@ async function handler(
|
|||
});
|
||||
|
||||
retriedCount++;
|
||||
console.log(`Retried job ${job.id} with 3 new attempts`);
|
||||
console.log(`Retried S3 delete job ${job.id} with new attempts`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to retry job ${job.id}:`, error);
|
||||
console.error(`Failed to retry S3 delete job ${job.id}:`, error);
|
||||
}
|
||||
},
|
||||
100
|
||||
);
|
||||
|
||||
return {
|
||||
message: 'Successfully retried all failed S3 delete jobs with 3 new attempts',
|
||||
retriedCount,
|
||||
failedCount: failedJobs.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 功能2和3: 处理 OutLink 记录的数据迁移
|
||||
* - 添加 showFullText 字段
|
||||
* - 重命名现有字段
|
||||
*/
|
||||
async function migrateOutLinkData(): Promise<{
|
||||
totalRecords: number;
|
||||
updatedRecords: number;
|
||||
updateResults: Array<{
|
||||
operation: string;
|
||||
updated: number;
|
||||
}>;
|
||||
}> {
|
||||
let totalUpdated = 0;
|
||||
const updateResults: Array<{
|
||||
operation: string;
|
||||
updated: number;
|
||||
}> = [];
|
||||
|
||||
// 获取 MongoDB 原生集合,绕过 Mongoose 的严格模式
|
||||
const db = connectionMongo.connection.db;
|
||||
if (!db) {
|
||||
throw new Error('Database connection not established');
|
||||
}
|
||||
const outLinkCollection = db.collection('outlinks');
|
||||
|
||||
// 1. 为所有 share 类型的记录添加 showFullText 字段
|
||||
const shareLinks = await outLinkCollection
|
||||
.find({
|
||||
type: PublishChannelEnum.share,
|
||||
showFullText: { $exists: false } // 只查找没有 showFullText 字段的记录
|
||||
})
|
||||
.toArray();
|
||||
|
||||
if (shareLinks.length > 0) {
|
||||
// 批量更新添加 showFullText 字段
|
||||
const showFullTextOps = shareLinks.map((link: any) => ({
|
||||
updateOne: {
|
||||
filter: { _id: link._id },
|
||||
update: { $set: { showFullText: link.showRawSource ?? true } }
|
||||
}
|
||||
}));
|
||||
|
||||
const showFullTextResult = await outLinkCollection.bulkWrite(showFullTextOps);
|
||||
totalUpdated += showFullTextResult.modifiedCount;
|
||||
updateResults.push({
|
||||
operation: 'Add showFullText field',
|
||||
updated: showFullTextResult.modifiedCount
|
||||
});
|
||||
|
||||
console.log(`Added showFullText field to ${showFullTextResult.modifiedCount} share links`);
|
||||
}
|
||||
|
||||
// 2. 重命名字段:showNodeStatus -> showRunningStatus
|
||||
const showNodeStatusLinks = await outLinkCollection
|
||||
.find({
|
||||
showNodeStatus: { $exists: true },
|
||||
showRunningStatus: { $exists: false }
|
||||
})
|
||||
.toArray();
|
||||
|
||||
if (showNodeStatusLinks.length > 0) {
|
||||
const renameNodeStatusOps = showNodeStatusLinks.map((link: any) => ({
|
||||
updateOne: {
|
||||
filter: { _id: link._id },
|
||||
update: [
|
||||
{
|
||||
$set: { showRunningStatus: '$showNodeStatus' }
|
||||
},
|
||||
{
|
||||
$unset: 'showNodeStatus'
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
|
||||
const renameNodeStatusResult = await outLinkCollection.bulkWrite(renameNodeStatusOps);
|
||||
totalUpdated += renameNodeStatusResult.modifiedCount;
|
||||
updateResults.push({
|
||||
operation: 'Rename showNodeStatus to showRunningStatus',
|
||||
updated: renameNodeStatusResult.modifiedCount
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Renamed showNodeStatus to showRunningStatus for ${renameNodeStatusResult.modifiedCount} links`
|
||||
);
|
||||
}
|
||||
|
||||
// 3. 重命名字段:responseDetail -> showCite
|
||||
const responseDetailLinks = await outLinkCollection
|
||||
.find({
|
||||
responseDetail: { $exists: true },
|
||||
showCite: { $exists: false }
|
||||
})
|
||||
.toArray();
|
||||
|
||||
if (responseDetailLinks.length > 0) {
|
||||
const renameResponseDetailOps = responseDetailLinks.map((link: any) => ({
|
||||
updateOne: {
|
||||
filter: { _id: link._id },
|
||||
update: [
|
||||
{
|
||||
$set: { showCite: '$responseDetail' }
|
||||
},
|
||||
{
|
||||
$unset: 'responseDetail'
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
|
||||
const renameResponseDetailResult = await outLinkCollection.bulkWrite(renameResponseDetailOps);
|
||||
totalUpdated += renameResponseDetailResult.modifiedCount;
|
||||
updateResults.push({
|
||||
operation: 'Rename responseDetail to showCite',
|
||||
updated: renameResponseDetailResult.modifiedCount
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Renamed responseDetail to showCite for ${renameResponseDetailResult.modifiedCount} links`
|
||||
);
|
||||
}
|
||||
|
||||
// 4. 重命名字段:showRawSource -> canDownloadSource
|
||||
const showRawSourceLinks = await outLinkCollection
|
||||
.find({
|
||||
showRawSource: { $exists: true },
|
||||
canDownloadSource: { $exists: false }
|
||||
})
|
||||
.toArray();
|
||||
|
||||
if (showRawSourceLinks.length > 0) {
|
||||
const renameRawSourceOps = showRawSourceLinks.map((link: any) => ({
|
||||
updateOne: {
|
||||
filter: { _id: link._id },
|
||||
update: [
|
||||
{
|
||||
$set: { canDownloadSource: '$showRawSource' }
|
||||
},
|
||||
{
|
||||
$unset: 'showRawSource'
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
|
||||
const renameRawSourceResult = await outLinkCollection.bulkWrite(renameRawSourceOps);
|
||||
totalUpdated += renameRawSourceResult.modifiedCount;
|
||||
updateResults.push({
|
||||
operation: 'Rename showRawSource to canDownloadSource',
|
||||
updated: renameRawSourceResult.modifiedCount
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Renamed showRawSource to canDownloadSource for ${renameRawSourceResult.modifiedCount} links`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
totalRecords: totalUpdated,
|
||||
updatedRecords: totalUpdated,
|
||||
updateResults
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 主处理函数
|
||||
*/
|
||||
async function handler(
|
||||
req: ApiRequestProps,
|
||||
_res: ApiResponseType<ResponseType>
|
||||
): Promise<ResponseType> {
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
// 执行功能1: 重试 S3 删除任务
|
||||
const s3JobResult = await retryFailedS3DeleteJobs();
|
||||
|
||||
// 执行功能2&3: OutLink 数据迁移
|
||||
let shareLinkMigration = {
|
||||
totalRecords: 0,
|
||||
updatedRecords: 0,
|
||||
updateResults: [] as Array<{ operation: string; updated: number }>
|
||||
};
|
||||
|
||||
try {
|
||||
shareLinkMigration = await migrateOutLinkData();
|
||||
} catch (error) {
|
||||
console.error('Failed to migrate outLink data:', error);
|
||||
// 即使迁移失败,也继续返回 S3 任务处理的结果
|
||||
}
|
||||
|
||||
return {
|
||||
message: `Completed v4.14.5 initialization: S3 job retries and outLink migration`,
|
||||
retriedCount: s3JobResult.retriedCount,
|
||||
failedCount: s3JobResult.failedCount,
|
||||
shareLinkMigration
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ async function handler(
|
|||
};
|
||||
}
|
||||
|
||||
const [app, { responseDetail, showNodeStatus, authType }] = await Promise.all([
|
||||
const [app, { showCite, showRunningStatus, authType }] = await Promise.all([
|
||||
MongoApp.findById(appId, 'type').lean(),
|
||||
authChatCrud({
|
||||
req,
|
||||
|
|
@ -81,16 +81,16 @@ async function handler(
|
|||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({
|
||||
nodeRespones: item.responseData,
|
||||
responseDetail
|
||||
responseDetail: showCite
|
||||
});
|
||||
|
||||
if (showNodeStatus === false) {
|
||||
if (showRunningStatus === false) {
|
||||
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!responseDetail) {
|
||||
if (!showCite) {
|
||||
histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.value = removeAIResponseCite(item.value, false);
|
||||
|
|
@ -99,7 +99,7 @@ async function handler(
|
|||
}
|
||||
|
||||
return {
|
||||
list: isPlugin ? histories : transformPreviewHistories(histories, responseDetail),
|
||||
list: isPlugin ? histories : transformPreviewHistories(histories, showCite),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ async function handler(
|
|||
};
|
||||
}
|
||||
|
||||
const [app, { responseDetail, showNodeStatus, authType }] = await Promise.all([
|
||||
const [app, { showCite, showRunningStatus, authType }] = await Promise.all([
|
||||
MongoApp.findById(appId, 'type').lean(),
|
||||
authChatCrud({
|
||||
req,
|
||||
|
|
@ -93,16 +93,16 @@ async function handler(
|
|||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.responseData = filterPublicNodeResponseData({
|
||||
nodeRespones: item.responseData,
|
||||
responseDetail
|
||||
responseDetail: showCite
|
||||
});
|
||||
|
||||
if (showNodeStatus === false) {
|
||||
if (showRunningStatus === false) {
|
||||
item.value = item.value.filter((v) => v.type !== ChatItemValueTypeEnum.tool);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!responseDetail) {
|
||||
if (!showCite) {
|
||||
result.histories.forEach((item) => {
|
||||
if (item.obj === ChatRoleEnum.AI) {
|
||||
item.value = removeAIResponseCite(item.value, false);
|
||||
|
|
@ -110,9 +110,7 @@ async function handler(
|
|||
});
|
||||
}
|
||||
|
||||
const list = isPlugin
|
||||
? result.histories
|
||||
: transformPreviewHistories(result.histories, responseDetail);
|
||||
const list = isPlugin ? result.histories : transformPreviewHistories(result.histories, showCite);
|
||||
|
||||
return {
|
||||
list: list.map((item) => ({
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ async function handler(
|
|||
return [];
|
||||
}
|
||||
|
||||
const [{ responseDetail }, chatData, nodeResponses] = await Promise.all([
|
||||
const [{ showCite }, chatData, nodeResponses] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
|
|
@ -57,7 +57,7 @@ async function handler(
|
|||
const flowResponses = chatData.responseData?.length ? chatData.responseData : nodeResponses;
|
||||
return req.query.shareId
|
||||
? filterPublicNodeResponseData({
|
||||
responseDetail,
|
||||
responseDetail: showCite,
|
||||
nodeRespones: flowResponses
|
||||
})
|
||||
: flowResponses;
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ async function handler(
|
|||
|
||||
const limitedPageSize = Math.min(pageSize, 30);
|
||||
|
||||
const [collection, { chat, showRawSource }, chatItem] = await Promise.all([
|
||||
const [collection, { chat, showFullText }, chatItem] = await Promise.all([
|
||||
getCollectionWithDataset(collectionId),
|
||||
authChatCrud({
|
||||
req,
|
||||
|
|
@ -73,7 +73,7 @@ async function handler(
|
|||
authCollectionInChat({ appId, chatId, chatItemDataId, collectionIds: [collectionId] })
|
||||
]);
|
||||
|
||||
if (!showRawSource || !chat || !chatItem || initialAnchor === undefined) {
|
||||
if (!showFullText || !chat || !chatItem || initialAnchor === undefined) {
|
||||
return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ async function handler(req: ApiRequestProps<GetQuoteProps>): Promise<GetQuotesRe
|
|||
datasetDataIdList
|
||||
} = req.body;
|
||||
|
||||
const [{ chat, responseDetail }, chatItem] = await Promise.all([
|
||||
const [{ chat, showCite }, chatItem] = await Promise.all([
|
||||
authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
|
|
@ -53,7 +53,7 @@ async function handler(req: ApiRequestProps<GetQuoteProps>): Promise<GetQuotesRe
|
|||
MongoChatItem.findOne({ appId, chatId, dataId: chatItemDataId }, 'time').lean(),
|
||||
authCollectionInChat({ appId, chatId, chatItemDataId, collectionIds: collectionIdList })
|
||||
]);
|
||||
if (!chat || !chatItem || !responseDetail) return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
if (!chat || !chatItem || !showCite) return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
|
||||
const list = await MongoDatasetData.find(
|
||||
{ _id: { $in: datasetDataIdList }, collectionId: { $in: collectionIdList } },
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ async function handler(req: ApiRequestProps<ExportCollectionBody, {}>, res: Next
|
|||
authCollectionInChat({ appId, chatId, chatItemDataId, collectionIds: [collectionId] })
|
||||
]);
|
||||
|
||||
if (!authRes.showRawSource) {
|
||||
if (!authRes.canDownloadSource) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ async function handler(
|
|||
authCollectionInChat({ appId, chatId, chatItemDataId, collectionIds: [collectionId] })
|
||||
]);
|
||||
|
||||
if (!authRes.showRawSource) {
|
||||
if (!authRes.canDownloadSource) {
|
||||
return Promise.reject(DatasetErrEnum.unAuthDatasetFile);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuot
|
|||
return Promise.reject(i18nT('common:data_not_found'));
|
||||
}
|
||||
|
||||
const [collection, { responseDetail }] = await Promise.all([
|
||||
const [collection, { showCite }] = await Promise.all([
|
||||
MongoDatasetCollection.findById(datasetData.collectionId).lean(),
|
||||
authChatCrud({
|
||||
req,
|
||||
|
|
@ -73,7 +73,7 @@ async function handler(req: ApiRequestProps<GetQuoteDataProps>): Promise<GetQuot
|
|||
if (!collection) {
|
||||
return Promise.reject('Can not find the collection');
|
||||
}
|
||||
if (!responseDetail) {
|
||||
if (!showCite) {
|
||||
return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import {
|
||||
type PlaygroundVisibilityConfigQuery,
|
||||
type PlaygroundVisibilityConfigResponse,
|
||||
PlaygroundVisibilityConfigQuerySchema,
|
||||
PlaygroundVisibilityConfigResponseSchema
|
||||
} from '@fastgpt/global/support/outLink/api.d';
|
||||
|
||||
async function handler(
|
||||
req: ApiRequestProps<{}, PlaygroundVisibilityConfigQuery>
|
||||
): Promise<PlaygroundVisibilityConfigResponse> {
|
||||
const { appId } = PlaygroundVisibilityConfigQuerySchema.parse(req.query);
|
||||
|
||||
await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
const existingConfig = await MongoOutLink.findOne(
|
||||
{
|
||||
appId,
|
||||
type: PublishChannelEnum.playground
|
||||
},
|
||||
'showRunningStatus showCite showFullText canDownloadSource'
|
||||
).lean();
|
||||
|
||||
return PlaygroundVisibilityConfigResponseSchema.parse({
|
||||
showRunningStatus: existingConfig?.showRunningStatus ?? true,
|
||||
showCite: existingConfig?.showCite ?? true,
|
||||
showFullText: existingConfig?.showFullText ?? true,
|
||||
canDownloadSource: existingConfig?.canDownloadSource ?? true
|
||||
});
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import {
|
||||
type UpdatePlaygroundVisibilityConfigBody,
|
||||
UpdatePlaygroundVisibilityConfigBodySchema
|
||||
} from '@fastgpt/global/support/outLink/api.d';
|
||||
|
||||
async function handler(req: ApiRequestProps<UpdatePlaygroundVisibilityConfigBody, {}>) {
|
||||
const { appId, showRunningStatus, showCite, showFullText, canDownloadSource } =
|
||||
UpdatePlaygroundVisibilityConfigBodySchema.parse(req.body);
|
||||
|
||||
const { teamId, tmbId } = await authApp({
|
||||
req,
|
||||
authToken: true,
|
||||
appId,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
|
||||
await MongoOutLink.updateOne(
|
||||
{ appId, type: PublishChannelEnum.playground },
|
||||
{
|
||||
$setOnInsert: {
|
||||
shareId: `playground-${appId}`,
|
||||
teamId,
|
||||
tmbId,
|
||||
name: 'Playground Chat'
|
||||
},
|
||||
$set: {
|
||||
appId,
|
||||
type: PublishChannelEnum.playground,
|
||||
showRunningStatus: showRunningStatus,
|
||||
showCite: showCite,
|
||||
showFullText: showFullText,
|
||||
canDownloadSource: canDownloadSource
|
||||
}
|
||||
},
|
||||
{ upsert: true }
|
||||
);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
|
|
@ -26,7 +26,8 @@ export type OutLinkUpdateResponse = string;
|
|||
async function handler(
|
||||
req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery>
|
||||
): Promise<OutLinkUpdateResponse> {
|
||||
const { _id, name, responseDetail, limit, app, showRawSource, showNodeStatus } = req.body;
|
||||
const { _id, name, showCite, limit, app, canDownloadSource, showRunningStatus, showFullText } =
|
||||
req.body;
|
||||
|
||||
if (!_id) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
|
|
@ -46,10 +47,10 @@ async function handler(
|
|||
|
||||
const doc = await MongoOutLink.findByIdAndUpdate(_id, {
|
||||
name,
|
||||
responseDetail,
|
||||
showRawSource,
|
||||
showNodeStatus,
|
||||
// showFullText,
|
||||
showCite,
|
||||
canDownloadSource,
|
||||
showRunningStatus,
|
||||
showFullText,
|
||||
limit,
|
||||
app
|
||||
});
|
||||
|
|
|
|||
|
|
@ -91,8 +91,8 @@ type AuthResponseType = {
|
|||
teamId: string;
|
||||
tmbId: string;
|
||||
app: AppSchema;
|
||||
responseDetail?: boolean;
|
||||
showNodeStatus?: boolean;
|
||||
showCite?: boolean;
|
||||
showRunningStatus?: boolean;
|
||||
authType: `${AuthUserTypeEnum}`;
|
||||
apikey?: string;
|
||||
responseAllData: boolean;
|
||||
|
|
@ -157,13 +157,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
teamId,
|
||||
tmbId,
|
||||
app,
|
||||
responseDetail,
|
||||
showCite,
|
||||
authType,
|
||||
sourceName,
|
||||
apikey,
|
||||
responseAllData,
|
||||
outLinkUserId = customUid,
|
||||
showNodeStatus
|
||||
showRunningStatus
|
||||
} = await (async () => {
|
||||
// share chat
|
||||
if (shareId && outLinkUid) {
|
||||
|
|
@ -205,7 +205,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
pushTrack.teamChatQPM({ teamId });
|
||||
|
||||
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||
retainDatasetCite = retainDatasetCite && !!showCite;
|
||||
const isPlugin = app.type === AppTypeEnum.workflowTool;
|
||||
|
||||
// Check message type
|
||||
|
|
@ -275,7 +275,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
detail,
|
||||
streamResponse: stream,
|
||||
id: chatId,
|
||||
showNodeStatus
|
||||
showNodeStatus: showRunningStatus
|
||||
});
|
||||
|
||||
const saveChatId = chatId || getNanoid(24);
|
||||
|
|
@ -388,7 +388,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
/* select fe response field */
|
||||
const feResponseData = responseAllData
|
||||
? flowResponses
|
||||
: filterPublicNodeResponseData({ nodeRespones: flowResponses, responseDetail });
|
||||
: filterPublicNodeResponseData({ nodeRespones: flowResponses, responseDetail: showCite });
|
||||
|
||||
if (stream) {
|
||||
workflowResponseWrite({
|
||||
|
|
@ -508,7 +508,7 @@ const authShareChat = async ({
|
|||
shareId: string;
|
||||
chatId?: string;
|
||||
}): Promise<AuthResponseType> => {
|
||||
const { teamId, tmbId, appId, authType, responseDetail, showNodeStatus, uid, sourceName } =
|
||||
const { teamId, tmbId, appId, authType, showCite, showRunningStatus, uid, sourceName } =
|
||||
await authOutLinkChatStart(data);
|
||||
const app = await MongoApp.findById(appId).lean();
|
||||
|
||||
|
|
@ -530,9 +530,9 @@ const authShareChat = async ({
|
|||
apikey: '',
|
||||
authType,
|
||||
responseAllData: false,
|
||||
responseDetail,
|
||||
showCite,
|
||||
outLinkUserId: uid,
|
||||
showNodeStatus
|
||||
showRunningStatus
|
||||
};
|
||||
};
|
||||
const authTeamSpaceChat = async ({
|
||||
|
|
@ -569,7 +569,7 @@ const authTeamSpaceChat = async ({
|
|||
authType: AuthUserTypeEnum.outLink,
|
||||
apikey: '',
|
||||
responseAllData: false,
|
||||
responseDetail: true,
|
||||
showCite: true,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
};
|
||||
|
|
@ -651,7 +651,7 @@ const authHeaderRequest = async ({
|
|||
authType,
|
||||
sourceName,
|
||||
responseAllData: true,
|
||||
responseDetail: true
|
||||
showCite: true
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ type AuthResponseType = {
|
|||
teamId: string;
|
||||
tmbId: string;
|
||||
app: AppSchema;
|
||||
responseDetail?: boolean;
|
||||
showNodeStatus?: boolean;
|
||||
showCite?: boolean;
|
||||
showRunningStatus?: boolean;
|
||||
authType: `${AuthUserTypeEnum}`;
|
||||
apikey?: string;
|
||||
responseAllData: boolean;
|
||||
|
|
@ -158,13 +158,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
teamId,
|
||||
tmbId,
|
||||
app,
|
||||
responseDetail,
|
||||
showCite,
|
||||
authType,
|
||||
sourceName,
|
||||
apikey,
|
||||
responseAllData,
|
||||
outLinkUserId = customUid,
|
||||
showNodeStatus
|
||||
showRunningStatus
|
||||
} = await (async () => {
|
||||
// share chat
|
||||
if (shareId && outLinkUid) {
|
||||
|
|
@ -206,7 +206,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
|
||||
pushTrack.teamChatQPM({ teamId });
|
||||
|
||||
retainDatasetCite = retainDatasetCite && !!responseDetail;
|
||||
retainDatasetCite = retainDatasetCite && !!showCite;
|
||||
const isPlugin = app.type === AppTypeEnum.workflowTool;
|
||||
|
||||
// Check message type
|
||||
|
|
@ -275,7 +275,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
detail,
|
||||
streamResponse: stream,
|
||||
id: chatId,
|
||||
showNodeStatus
|
||||
showNodeStatus: showRunningStatus
|
||||
});
|
||||
|
||||
const saveChatId = chatId || getNanoid(24);
|
||||
|
|
@ -321,7 +321,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
maxRunTimes: WORKFLOW_MAX_RUN_TIMES,
|
||||
workflowStreamResponse: workflowResponseWrite,
|
||||
responseAllData,
|
||||
responseDetail
|
||||
responseDetail: showCite
|
||||
});
|
||||
}
|
||||
return Promise.reject('您的工作流版本过低,请重新发布一次');
|
||||
|
|
@ -390,7 +390,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||
/* select fe response field */
|
||||
const feResponseData = responseAllData
|
||||
? flowResponses
|
||||
: filterPublicNodeResponseData({ nodeRespones: flowResponses, responseDetail });
|
||||
: filterPublicNodeResponseData({ nodeRespones: flowResponses, responseDetail: showCite });
|
||||
|
||||
if (stream) {
|
||||
workflowResponseWrite({
|
||||
|
|
@ -503,7 +503,7 @@ const authShareChat = async ({
|
|||
shareId: string;
|
||||
chatId?: string;
|
||||
}): Promise<AuthResponseType> => {
|
||||
const { teamId, tmbId, appId, authType, responseDetail, showNodeStatus, uid, sourceName } =
|
||||
const { teamId, tmbId, appId, authType, showCite, showRunningStatus, uid, sourceName } =
|
||||
await authOutLinkChatStart(data);
|
||||
const app = await MongoApp.findById(appId).lean();
|
||||
|
||||
|
|
@ -525,9 +525,9 @@ const authShareChat = async ({
|
|||
apikey: '',
|
||||
authType,
|
||||
responseAllData: false,
|
||||
responseDetail,
|
||||
showCite,
|
||||
outLinkUserId: uid,
|
||||
showNodeStatus
|
||||
showRunningStatus
|
||||
};
|
||||
};
|
||||
const authTeamSpaceChat = async ({
|
||||
|
|
@ -564,7 +564,7 @@ const authTeamSpaceChat = async ({
|
|||
authType: AuthUserTypeEnum.outLink,
|
||||
apikey: '',
|
||||
responseAllData: false,
|
||||
responseDetail: true,
|
||||
showCite: true,
|
||||
outLinkUserId: uid
|
||||
};
|
||||
};
|
||||
|
|
@ -646,7 +646,7 @@ const authHeaderRequest = async ({
|
|||
authType,
|
||||
sourceName,
|
||||
responseAllData: true,
|
||||
responseDetail: true
|
||||
showCite: true
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ import ChatTeamApp from '@/pageComponents/chat/ChatTeamApp';
|
|||
import ChatFavouriteApp from '@/pageComponents/chat/ChatFavouriteApp';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import type { LoginSuccessResponse } from '@/global/support/api/userRes';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { addLog } from '@fastgpt/service/common/system/log';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
|
||||
const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
||||
const { isPc } = useSystem();
|
||||
|
|
@ -88,7 +91,14 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const Render = (props: { appId: string; isStandalone?: string }) => {
|
||||
const Render = (props: {
|
||||
appId: string;
|
||||
isStandalone?: string;
|
||||
showRunningStatus: boolean;
|
||||
showCite: boolean;
|
||||
showFullText: boolean;
|
||||
canDownloadSource: boolean;
|
||||
}) => {
|
||||
const { appId, isStandalone } = props;
|
||||
const { chatId } = useChatStore();
|
||||
const { setUserInfo } = useUserStore();
|
||||
|
|
@ -138,9 +148,10 @@ const Render = (props: { appId: string; isStandalone?: string }) => {
|
|||
<ChatContextProvider params={chatHistoryProviderParams}>
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={isStandalone !== '1'}
|
||||
isShowReadRawSource={true}
|
||||
isResponseDetail={true}
|
||||
showNodeStatus
|
||||
showRunningStatus={props.showRunningStatus}
|
||||
canDownloadSource={props.canDownloadSource}
|
||||
isShowCite={props.showCite}
|
||||
isShowFullText={props.showFullText}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<Chat myApps={myApps} />
|
||||
|
|
@ -151,14 +162,38 @@ const Render = (props: { appId: string; isStandalone?: string }) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default Render;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const appId = context?.query?.appId || '';
|
||||
|
||||
const chatQuoteReaderConfig = await (async () => {
|
||||
try {
|
||||
if (!appId) return null;
|
||||
|
||||
const config = await MongoOutLink.findOne(
|
||||
{
|
||||
appId,
|
||||
type: PublishChannelEnum.playground
|
||||
},
|
||||
'showRunningStatus showCite showFullText canDownloadSource'
|
||||
).lean();
|
||||
|
||||
return config;
|
||||
} catch (error) {
|
||||
addLog.error('getServerSideProps', error);
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
props: {
|
||||
appId: context?.query?.appId || '',
|
||||
isStandalone: context?.query?.isStandalone || '',
|
||||
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow', 'user', 'login']))
|
||||
appId,
|
||||
showRunningStatus: chatQuoteReaderConfig?.showRunningStatus ?? true,
|
||||
showCite: chatQuoteReaderConfig?.showCite ?? true,
|
||||
showFullText: chatQuoteReaderConfig?.showFullText ?? true,
|
||||
canDownloadSource: chatQuoteReaderConfig?.canDownloadSource ?? true,
|
||||
...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow']))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default Render;
|
||||
|
|
|
|||
|
|
@ -53,10 +53,10 @@ type Props = {
|
|||
shareId: string;
|
||||
authToken: string;
|
||||
customUid: string;
|
||||
showRawSource: boolean;
|
||||
responseDetail: boolean;
|
||||
// showFullText: boolean;
|
||||
showNodeStatus: boolean;
|
||||
canDownloadSource: boolean;
|
||||
isShowCite: boolean;
|
||||
isShowFullText: boolean;
|
||||
showRunningStatus: boolean;
|
||||
};
|
||||
|
||||
const OutLink = (props: Props) => {
|
||||
|
|
@ -95,7 +95,7 @@ const OutLink = (props: Props) => {
|
|||
const setChatBoxData = useContextSelector(ChatItemContext, (v) => v.setChatBoxData);
|
||||
const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData);
|
||||
const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData);
|
||||
const isResponseDetail = useContextSelector(ChatItemContext, (v) => v.isResponseDetail);
|
||||
const isShowCite = useContextSelector(ChatItemContext, (v) => v.isShowCite);
|
||||
|
||||
const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords);
|
||||
const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount);
|
||||
|
|
@ -175,7 +175,7 @@ const OutLink = (props: Props) => {
|
|||
responseChatItemId,
|
||||
chatId: completionChatId,
|
||||
...outLinkAuthData,
|
||||
retainDatasetCite: isResponseDetail
|
||||
retainDatasetCite: isShowCite
|
||||
},
|
||||
onMessage: generatingMessage,
|
||||
abortCtrl: controller
|
||||
|
|
@ -213,7 +213,7 @@ const OutLink = (props: Props) => {
|
|||
chatId,
|
||||
customVariables,
|
||||
outLinkAuthData,
|
||||
isResponseDetail,
|
||||
isShowCite,
|
||||
onUpdateHistoryTitle,
|
||||
setChatBoxData,
|
||||
forbidLoadChat,
|
||||
|
|
@ -388,10 +388,10 @@ const Render = (props: Props) => {
|
|||
<ChatContextProvider params={chatHistoryProviderParams}>
|
||||
<ChatItemContextProvider
|
||||
showRouteToDatasetDetail={false}
|
||||
isShowReadRawSource={props.showRawSource}
|
||||
isResponseDetail={props.responseDetail}
|
||||
// isShowFullText={props.showFullText}
|
||||
showNodeStatus={props.showNodeStatus}
|
||||
canDownloadSource={props.canDownloadSource}
|
||||
isShowCite={props.isShowCite}
|
||||
isShowFullText={props.isShowFullText}
|
||||
showRunningStatus={props.showRunningStatus}
|
||||
>
|
||||
<ChatRecordContextProvider params={chatRecordProviderParams}>
|
||||
<OutLink {...props} />
|
||||
|
|
@ -416,7 +416,7 @@ export async function getServerSideProps(context: any) {
|
|||
{
|
||||
shareId
|
||||
},
|
||||
'appId showRawSource showNodeStatus responseDetail'
|
||||
'appId canDownloadSource showCite showFullText showRunningStatus'
|
||||
)
|
||||
.populate<{ associatedApp: AppSchema }>('associatedApp', 'name avatar intro')
|
||||
.lean();
|
||||
|
|
@ -432,10 +432,10 @@ export async function getServerSideProps(context: any) {
|
|||
appName: app?.associatedApp?.name ?? 'AI',
|
||||
appAvatar: app?.associatedApp?.avatar ?? '',
|
||||
appIntro: app?.associatedApp?.intro ?? 'AI',
|
||||
showRawSource: app?.showRawSource ?? false,
|
||||
responseDetail: app?.responseDetail ?? false,
|
||||
// showFullText: app?.showFullText ?? false,
|
||||
showNodeStatus: app?.showNodeStatus ?? false,
|
||||
canDownloadSource: app?.canDownloadSource ?? false,
|
||||
isShowCite: app?.showCite ?? false,
|
||||
isShowFullText: app?.showFullText ?? false,
|
||||
showRunningStatus: app?.showRunningStatus ?? false,
|
||||
shareId: shareId ?? '',
|
||||
authToken: authToken ?? '',
|
||||
customUid,
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
|||
Chat没有读写的权限之分,鉴权过了,都可以操作。
|
||||
*/
|
||||
export const defaultResponseShow = {
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true
|
||||
};
|
||||
type AuthChatCommonProps = {
|
||||
appId: string;
|
||||
|
|
@ -54,9 +55,10 @@ export async function authChatCrud({
|
|||
tmbId: string;
|
||||
uid: string;
|
||||
chat?: ChatSchemaType;
|
||||
responseDetail: boolean;
|
||||
showNodeStatus: boolean;
|
||||
showRawSource: boolean;
|
||||
showCite: boolean;
|
||||
showRunningStatus: boolean;
|
||||
showFullText: boolean;
|
||||
canDownloadSource: boolean;
|
||||
authType?: `${AuthUserTypeEnum}`;
|
||||
}> {
|
||||
if (!appId) return Promise.reject(ChatErrEnum.unAuthChat);
|
||||
|
|
@ -109,9 +111,11 @@ export async function authChatCrud({
|
|||
teamId: String(outLinkConfig.teamId),
|
||||
tmbId: String(outLinkConfig.tmbId),
|
||||
uid,
|
||||
responseDetail: outLinkConfig.responseDetail,
|
||||
showNodeStatus: outLinkConfig.showNodeStatus ?? true,
|
||||
showRawSource: outLinkConfig.showRawSource ?? false,
|
||||
|
||||
showCite: outLinkConfig.showCite ?? false,
|
||||
showRunningStatus: outLinkConfig.showRunningStatus ?? true,
|
||||
showFullText: outLinkConfig.showFullText ?? false,
|
||||
canDownloadSource: outLinkConfig.canDownloadSource ?? false,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
};
|
||||
}
|
||||
|
|
@ -123,9 +127,10 @@ export async function authChatCrud({
|
|||
teamId: String(outLinkConfig.teamId),
|
||||
tmbId: String(outLinkConfig.tmbId),
|
||||
uid,
|
||||
responseDetail: outLinkConfig.responseDetail,
|
||||
showNodeStatus: outLinkConfig.showNodeStatus ?? true,
|
||||
showRawSource: outLinkConfig.showRawSource ?? false,
|
||||
showCite: outLinkConfig.showCite ?? false,
|
||||
showRunningStatus: outLinkConfig.showRunningStatus ?? true,
|
||||
showFullText: outLinkConfig.showFullText ?? false,
|
||||
canDownloadSource: outLinkConfig.canDownloadSource ?? false,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
};
|
||||
}
|
||||
|
|
@ -135,9 +140,10 @@ export async function authChatCrud({
|
|||
tmbId: String(outLinkConfig.tmbId),
|
||||
chat,
|
||||
uid,
|
||||
responseDetail: outLinkConfig.responseDetail,
|
||||
showNodeStatus: outLinkConfig.showNodeStatus ?? true,
|
||||
showRawSource: outLinkConfig.showRawSource ?? false,
|
||||
showCite: outLinkConfig.showCite ?? false,
|
||||
showRunningStatus: outLinkConfig.showRunningStatus ?? true,
|
||||
showFullText: outLinkConfig.showFullText ?? false,
|
||||
canDownloadSource: outLinkConfig.canDownloadSource ?? false,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,8 +63,10 @@ export async function authOutLinkChatStart({
|
|||
teamId: outLinkConfig.teamId,
|
||||
tmbId: outLinkConfig.tmbId,
|
||||
authType: AuthUserTypeEnum.token,
|
||||
responseDetail: outLinkConfig.responseDetail,
|
||||
showNodeStatus: outLinkConfig.showNodeStatus,
|
||||
showCite: outLinkConfig.showCite,
|
||||
showRunningStatus: outLinkConfig.showRunningStatus,
|
||||
showFullText: outLinkConfig.showFullText,
|
||||
canDownloadSource: outLinkConfig.canDownloadSource,
|
||||
appId,
|
||||
uid
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ export const defaultApp: AppDetailType = {
|
|||
|
||||
export const defaultOutLinkForm: OutLinkEditType = {
|
||||
name: '',
|
||||
showNodeStatus: true,
|
||||
responseDetail: false,
|
||||
// showFullText: false,
|
||||
showRawSource: false,
|
||||
showRunningStatus: true,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false,
|
||||
limit: {
|
||||
QPM: 100,
|
||||
maxUsagePoints: -1
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import { type OutLinkChatAuthProps } from '@fastgpt/global/support/permission/ch
|
|||
|
||||
type ContextProps = {
|
||||
showRouteToDatasetDetail: boolean;
|
||||
isShowReadRawSource: boolean;
|
||||
isResponseDetail: boolean;
|
||||
// isShowFullText: boolean;
|
||||
showNodeStatus: boolean;
|
||||
canDownloadSource: boolean;
|
||||
isShowCite: boolean;
|
||||
isShowFullText: boolean;
|
||||
showRunningStatus: boolean;
|
||||
};
|
||||
type ChatBoxDataType = {
|
||||
chatId?: string;
|
||||
|
|
@ -120,10 +120,10 @@ export const ChatItemContext = createContext<ChatItemContextType>({
|
|||
const ChatItemContextProvider = ({
|
||||
children,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
isResponseDetail,
|
||||
// isShowFullText,
|
||||
showNodeStatus
|
||||
canDownloadSource,
|
||||
isShowCite,
|
||||
isShowFullText,
|
||||
showRunningStatus
|
||||
}: {
|
||||
children: ReactNode;
|
||||
} & ContextProps) => {
|
||||
|
|
@ -196,10 +196,10 @@ const ChatItemContextProvider = ({
|
|||
resetVariables,
|
||||
clearChatRecords,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
isResponseDetail,
|
||||
// isShowFullText,
|
||||
showNodeStatus,
|
||||
canDownloadSource,
|
||||
isShowCite,
|
||||
isShowFullText,
|
||||
showRunningStatus,
|
||||
|
||||
datasetCiteData,
|
||||
setCiteModalData,
|
||||
|
|
@ -214,10 +214,10 @@ const ChatItemContextProvider = ({
|
|||
resetVariables,
|
||||
clearChatRecords,
|
||||
showRouteToDatasetDetail,
|
||||
isShowReadRawSource,
|
||||
isResponseDetail,
|
||||
// isShowFullText,
|
||||
showNodeStatus,
|
||||
canDownloadSource,
|
||||
isShowCite,
|
||||
showRunningStatus,
|
||||
isShowFullText,
|
||||
datasetCiteData,
|
||||
setCiteModalData,
|
||||
isVariableVisible,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import type {
|
||||
PlaygroundVisibilityConfigQuery,
|
||||
PlaygroundVisibilityConfigResponse,
|
||||
UpdatePlaygroundVisibilityConfigBody
|
||||
} from '@fastgpt/global/support/outLink/api';
|
||||
import { GET, POST, DELETE } from '@/web/common/api/request';
|
||||
import type {
|
||||
OutlinkAppType,
|
||||
|
|
@ -36,6 +41,14 @@ export function updateShareChat<T extends OutlinkAppType>(data: OutLinkEditType<
|
|||
return POST<string>(`/support/outLink/update`, data);
|
||||
}
|
||||
|
||||
export function getPlaygroundVisibilityConfig(data: PlaygroundVisibilityConfigQuery) {
|
||||
return GET<PlaygroundVisibilityConfigResponse>('/support/outLink/playground/config', data);
|
||||
}
|
||||
|
||||
export function updatePlaygroundVisibilityConfig(data: UpdatePlaygroundVisibilityConfigBody) {
|
||||
return POST<string>(`/support/outLink/playground/update`, data);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * create a shareChat
|
||||
// */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,175 @@
|
|||
import type { PlaygroundVisibilityConfigResponse } from '@fastgpt/global/support/outLink/api.d';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { getRootUser } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
|
||||
import * as configApi from '@/pages/api/support/outLink/playground/config';
|
||||
|
||||
describe('Playground Visibility Config API', () => {
|
||||
let rootUser: any;
|
||||
let testApp: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
rootUser = await getRootUser();
|
||||
|
||||
// Create a test app owned by the root user
|
||||
testApp = await MongoApp.create({
|
||||
name: 'Test App for Playground Config',
|
||||
type: 'simple',
|
||||
tmbId: rootUser.tmbId,
|
||||
teamId: rootUser.teamId
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up any created OutLink configs
|
||||
await MongoOutLink.deleteMany({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Clean up test data
|
||||
await MongoApp.deleteOne({ _id: testApp._id });
|
||||
});
|
||||
|
||||
it('should return default config values when no existing config found', async () => {
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
auth: rootUser,
|
||||
query: {
|
||||
appId: testApp._id
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.data).toEqual({
|
||||
showRunningStatus: true,
|
||||
showCite: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true
|
||||
});
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return existing config values when config exists', async () => {
|
||||
// Create an existing config
|
||||
await MongoOutLink.create({
|
||||
shareId: `playground-${testApp._id}`,
|
||||
teamId: rootUser.teamId,
|
||||
tmbId: rootUser.tmbId,
|
||||
appId: testApp._id,
|
||||
name: 'Playground Chat',
|
||||
type: PublishChannelEnum.playground,
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false,
|
||||
usagePoints: 0,
|
||||
lastTime: new Date()
|
||||
});
|
||||
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
auth: rootUser,
|
||||
query: {
|
||||
appId: testApp._id
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.data).toEqual({
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false
|
||||
});
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return 500 when appId is missing', async () => {
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
auth: rootUser,
|
||||
query: {}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return 500 when appId is empty string', async () => {
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
auth: rootUser,
|
||||
query: {
|
||||
appId: ''
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle mixed config values correctly', async () => {
|
||||
// Create config with mixed true/false values
|
||||
await MongoOutLink.create({
|
||||
shareId: `playground-${testApp._id}`,
|
||||
teamId: rootUser.teamId,
|
||||
tmbId: rootUser.tmbId,
|
||||
appId: testApp._id,
|
||||
name: 'Playground Chat',
|
||||
type: PublishChannelEnum.playground,
|
||||
showRunningStatus: true,
|
||||
showCite: false,
|
||||
showFullText: true,
|
||||
canDownloadSource: false,
|
||||
usagePoints: 0,
|
||||
lastTime: new Date()
|
||||
});
|
||||
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
auth: rootUser,
|
||||
query: {
|
||||
appId: testApp._id
|
||||
}
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
expect(res.data).toEqual({
|
||||
showRunningStatus: true,
|
||||
showCite: false,
|
||||
showFullText: true,
|
||||
canDownloadSource: false
|
||||
});
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return error when user is not authenticated', async () => {
|
||||
const res = await Call<PlaygroundVisibilityConfigResponse>(configApi.default, {
|
||||
query: {
|
||||
appId: testApp._id
|
||||
}
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
import type { UpdatePlaygroundVisibilityConfigBody } from '@fastgpt/global/support/outLink/api.d';
|
||||
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { getRootUser } from '@test/datas/users';
|
||||
import { Call } from '@test/utils/request';
|
||||
import { describe, it, expect, beforeAll, afterEach, afterAll } from 'vitest';
|
||||
import * as updateApi from '@/pages/api/support/outLink/playground/update';
|
||||
|
||||
describe('Playground Visibility Update API', () => {
|
||||
let rootUser: any;
|
||||
let testApp: any;
|
||||
|
||||
beforeAll(async () => {
|
||||
rootUser = await getRootUser();
|
||||
|
||||
// Create a test app owned by the root user
|
||||
testApp = await MongoApp.create({
|
||||
name: 'Test App for Playground Update',
|
||||
type: 'simple',
|
||||
tmbId: rootUser.tmbId,
|
||||
teamId: rootUser.teamId
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up any created OutLink configs
|
||||
await MongoOutLink.deleteMany({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Clean up test data
|
||||
await MongoApp.deleteOne({ _id: testApp._id });
|
||||
});
|
||||
|
||||
it('should handle update request with valid data', async () => {
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: testApp._id,
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
|
||||
// Verify the config was created in database
|
||||
const createdConfig = await MongoOutLink.findOne({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
}).lean();
|
||||
|
||||
if (createdConfig) {
|
||||
expect(createdConfig.appId).toBe(testApp._id);
|
||||
expect(createdConfig.type).toBe(PublishChannelEnum.playground);
|
||||
expect(createdConfig.showRunningStatus).toBe(false);
|
||||
expect(createdConfig.showCite).toBe(false);
|
||||
expect(createdConfig.showFullText).toBe(false);
|
||||
expect(createdConfig.canDownloadSource).toBe(false);
|
||||
}
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle update request with true values', async () => {
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: testApp._id,
|
||||
showRunningStatus: true,
|
||||
showCite: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
|
||||
// Verify true values were set
|
||||
const createdConfig = await MongoOutLink.findOne({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
}).lean();
|
||||
|
||||
if (createdConfig) {
|
||||
expect(createdConfig.showRunningStatus).toBe(true);
|
||||
expect(createdConfig.showCite).toBe(true);
|
||||
expect(createdConfig.showFullText).toBe(true);
|
||||
expect(createdConfig.canDownloadSource).toBe(true);
|
||||
}
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle update request with mixed boolean values', async () => {
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: testApp._id,
|
||||
showRunningStatus: false,
|
||||
showCite: true,
|
||||
showFullText: false,
|
||||
canDownloadSource: true
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
|
||||
// Verify mixed values were set
|
||||
const createdConfig = await MongoOutLink.findOne({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
}).lean();
|
||||
|
||||
if (createdConfig) {
|
||||
expect(createdConfig.showRunningStatus).toBe(false);
|
||||
expect(createdConfig.showCite).toBe(true);
|
||||
expect(createdConfig.showFullText).toBe(false);
|
||||
expect(createdConfig.canDownloadSource).toBe(true);
|
||||
}
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('should return 500 when appId is missing', async () => {
|
||||
const updateData = {
|
||||
showRunningStatus: false
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return 500 when appId is empty string', async () => {
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: '',
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return error when user is not authenticated', async () => {
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: testApp._id,
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: false
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
body: updateData
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should validate all boolean fields are required', async () => {
|
||||
// Test with missing boolean fields (should fail validation)
|
||||
const updateData = {
|
||||
appId: testApp._id,
|
||||
showRunningStatus: false
|
||||
// Missing other boolean fields
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle updates for different apps independently', async () => {
|
||||
// Create a second test app
|
||||
const testApp2 = await MongoApp.create({
|
||||
name: 'Test App 2 for Playground Update',
|
||||
type: 'simple',
|
||||
tmbId: rootUser.tmbId,
|
||||
teamId: rootUser.teamId
|
||||
});
|
||||
|
||||
// Create config for first app
|
||||
await MongoOutLink.create({
|
||||
shareId: `playground-${testApp._id}`,
|
||||
teamId: rootUser.teamId,
|
||||
tmbId: rootUser.tmbId,
|
||||
appId: testApp._id,
|
||||
name: 'Playground Chat',
|
||||
type: PublishChannelEnum.playground,
|
||||
showRunningStatus: true,
|
||||
showCite: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
usagePoints: 0,
|
||||
lastTime: new Date()
|
||||
});
|
||||
|
||||
// Update config for second app
|
||||
const updateData: UpdatePlaygroundVisibilityConfigBody = {
|
||||
appId: testApp2._id,
|
||||
showRunningStatus: false,
|
||||
showCite: false,
|
||||
showFullText: true,
|
||||
canDownloadSource: true
|
||||
};
|
||||
|
||||
const res = await Call(updateApi.default, {
|
||||
auth: rootUser,
|
||||
body: updateData
|
||||
});
|
||||
|
||||
// Check if the request was processed successfully
|
||||
if (res.code === 200) {
|
||||
expect(res.error).toBeUndefined();
|
||||
|
||||
// Verify first app config is unchanged
|
||||
const config1 = await MongoOutLink.findOne({
|
||||
appId: testApp._id,
|
||||
type: PublishChannelEnum.playground
|
||||
}).lean();
|
||||
|
||||
if (config1) {
|
||||
expect(config1.showRunningStatus).toBe(true);
|
||||
expect(config1.showCite).toBe(true);
|
||||
}
|
||||
|
||||
// Verify second app config was created with new values
|
||||
const config2 = await MongoOutLink.findOne({
|
||||
appId: testApp2._id,
|
||||
type: PublishChannelEnum.playground
|
||||
}).lean();
|
||||
|
||||
if (config2) {
|
||||
expect(config2.showRunningStatus).toBe(false);
|
||||
expect(config2.showCite).toBe(false);
|
||||
expect(config2.showFullText).toBe(true);
|
||||
expect(config2.canDownloadSource).toBe(true);
|
||||
}
|
||||
} else {
|
||||
// If there are permission issues, we still expect the API to validate parameters
|
||||
expect(res.code).toBe(500);
|
||||
expect(res.error).toBeDefined();
|
||||
}
|
||||
|
||||
// Cleanup second app
|
||||
await MongoOutLink.deleteOne({ appId: testApp2._id });
|
||||
await MongoApp.deleteOne({ _id: testApp2._id });
|
||||
});
|
||||
});
|
||||
|
|
@ -80,9 +80,10 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -116,9 +117,10 @@ describe('authChatCrud', () => {
|
|||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
chat: mockChat,
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -145,9 +147,10 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -186,9 +189,9 @@ describe('authChatCrud', () => {
|
|||
outLinkConfig: {
|
||||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
canDownloadSource: true
|
||||
},
|
||||
uid: 'user1',
|
||||
appId: 'app1'
|
||||
|
|
@ -206,19 +209,20 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: false,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
});
|
||||
});
|
||||
|
||||
it('should auth outLink with default showNodeStatus and showRawSource', async () => {
|
||||
it('should auth outLink with default showRunningStatus and canDownloadSource', async () => {
|
||||
vi.mocked(authOutLink).mockResolvedValue({
|
||||
outLinkConfig: {
|
||||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
responseDetail: false,
|
||||
showCite: false,
|
||||
shareId: 'share1',
|
||||
outLinkUid: 'user1'
|
||||
},
|
||||
|
|
@ -238,9 +242,9 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
responseDetail: false,
|
||||
showNodeStatus: true, // default
|
||||
showRawSource: false, // default
|
||||
showCite: false,
|
||||
showRunningStatus: true, // default
|
||||
canDownloadSource: false, // default
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
});
|
||||
});
|
||||
|
|
@ -255,9 +259,9 @@ describe('authChatCrud', () => {
|
|||
outLinkConfig: {
|
||||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
canDownloadSource: true
|
||||
},
|
||||
uid: 'user1',
|
||||
appId: 'app1'
|
||||
|
|
@ -281,9 +285,10 @@ describe('authChatCrud', () => {
|
|||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
chat: mockChat,
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: false,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
});
|
||||
});
|
||||
|
|
@ -293,9 +298,9 @@ describe('authChatCrud', () => {
|
|||
outLinkConfig: {
|
||||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: false,
|
||||
showRawSource: true
|
||||
showCite: true,
|
||||
showRunningStatus: false,
|
||||
canDownloadSource: true
|
||||
},
|
||||
uid: 'user1',
|
||||
appId: 'app1'
|
||||
|
|
@ -318,9 +323,10 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'user1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: false,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: false,
|
||||
showFullText: false,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.outLink
|
||||
});
|
||||
});
|
||||
|
|
@ -335,7 +341,10 @@ describe('authChatCrud', () => {
|
|||
outLinkConfig: {
|
||||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
responseDetail: true
|
||||
showCite: true,
|
||||
showFullText: true,
|
||||
showRunningStatus: true,
|
||||
canDownloadSource: true
|
||||
},
|
||||
uid: 'user1',
|
||||
appId: 'app1'
|
||||
|
|
@ -428,9 +437,10 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'tmb1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -467,9 +477,10 @@ describe('authChatCrud', () => {
|
|||
tmbId: 'tmb1',
|
||||
uid: 'tmb1',
|
||||
chat: mockChat,
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -507,9 +518,10 @@ describe('authChatCrud', () => {
|
|||
tmbId: 'tmb1',
|
||||
uid: 'tmb1',
|
||||
chat: mockChat,
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
@ -539,9 +551,10 @@ describe('authChatCrud', () => {
|
|||
teamId: 'team1',
|
||||
tmbId: 'tmb1',
|
||||
uid: 'tmb1',
|
||||
responseDetail: true,
|
||||
showNodeStatus: true,
|
||||
showRawSource: true,
|
||||
showCite: true,
|
||||
showRunningStatus: true,
|
||||
showFullText: true,
|
||||
canDownloadSource: true,
|
||||
authType: AuthUserTypeEnum.teamDomain
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue