optimize app update time (#6127)

* 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>

* app update time

* recent app

* fix

* type

* fix

* context

* perf: update app usingtime code

* fix: ts

* update parent

* doc

* perf: code per

* unauth refresh

---------

Co-authored-by: archer <545436317@qq.com>
This commit is contained in:
heheer
2025-12-24 14:28:42 +08:00
committed by GitHub
parent ab743b9358
commit f175a1a30c
108 changed files with 2274 additions and 757 deletions

View File

@@ -1,6 +1,8 @@
import type { OpenAPIPath } from '../../type';
import { AppLogPath } from './log';
import { PublishChannelPath } from './publishChannel';
export const AppPath: OpenAPIPath = {
...AppLogPath
...AppLogPath,
...PublishChannelPath
};

View File

@@ -0,0 +1,5 @@
import { PlaygroundPath } from './playground';
export const PublishChannelPath = {
...PlaygroundPath
};

View File

@@ -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
>;

View File

@@ -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()
})
}
}
}
}
}
}
};

View File

@@ -1,72 +1,12 @@
import { OutLinkChatAuthSchema } from '../../../support/permission/chat/type';
import { ObjectIdSchema } from '../../../common/type/mongo';
import z from 'zod';
/* ============ v2/chat/stop ============ */
export const StopV2ChatSchema = z
.object({
/* Recently Used Apps */
export const GetRecentlyUsedAppsResponseSchema = z.array(
z.object({
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
name: z.string().min(1).describe('应用名称'),
avatar: z.string().min(1).describe('应用头像')
})
.meta({
example: {
appId: '1234567890',
chatId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type StopV2ChatParams = z.infer<typeof StopV2ChatSchema>;
export const StopV2ChatResponseSchema = z
.object({
success: z.boolean().describe('是否成功停止')
})
.meta({
example: {
success: true
}
});
export type StopV2ChatResponse = z.infer<typeof StopV2ChatResponseSchema>;
/* ============ chat file ============ */
export const PresignChatFileGetUrlSchema = z
.object({
key: z.string().min(1).describe('文件key'),
appId: ObjectIdSchema.describe('应用ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
.meta({
example: {
key: '1234567890',
appId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type PresignChatFileGetUrlParams = z.infer<typeof PresignChatFileGetUrlSchema>;
export const PresignChatFilePostUrlSchema = z
.object({
filename: z.string().min(1).describe('文件名'),
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
.meta({
example: {
filename: '1234567890',
appId: '1234567890',
chatId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type PresignChatFilePostUrlParams = z.infer<typeof PresignChatFilePostUrlSchema>;
);
export type GetRecentlyUsedAppsResponseType = z.infer<typeof GetRecentlyUsedAppsResponseSchema>;

View File

@@ -0,0 +1,72 @@
import { OutLinkChatAuthSchema } from '../../../../support/permission/chat/type';
import { ObjectIdSchema } from '../../../../common/type/mongo';
import z from 'zod';
/* ============ v2/chat/stop ============ */
export const StopV2ChatSchema = z
.object({
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
.meta({
example: {
appId: '1234567890',
chatId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type StopV2ChatParams = z.infer<typeof StopV2ChatSchema>;
export const StopV2ChatResponseSchema = z
.object({
success: z.boolean().describe('是否成功停止')
})
.meta({
example: {
success: true
}
});
export type StopV2ChatResponse = z.infer<typeof StopV2ChatResponseSchema>;
/* ============ chat file ============ */
export const PresignChatFileGetUrlSchema = z
.object({
key: z.string().min(1).describe('文件key'),
appId: ObjectIdSchema.describe('应用ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
.meta({
example: {
key: '1234567890',
appId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type PresignChatFileGetUrlParams = z.infer<typeof PresignChatFileGetUrlSchema>;
export const PresignChatFilePostUrlSchema = z
.object({
filename: z.string().min(1).describe('文件名'),
appId: ObjectIdSchema.describe('应用ID'),
chatId: z.string().min(1).describe('对话ID'),
outLinkAuthData: OutLinkChatAuthSchema.optional().describe('外链鉴权数据')
})
.meta({
example: {
filename: '1234567890',
appId: '1234567890',
chatId: '1234567890',
outLinkAuthData: {
shareId: '1234567890',
outLinkUid: '1234567890'
}
}
});
export type PresignChatFilePostUrlParams = z.infer<typeof PresignChatFilePostUrlSchema>;

View File

@@ -0,0 +1,86 @@
import type { OpenAPIPath } from '../../../type';
import { TagsMap } from '../../../tag';
import {
StopV2ChatSchema,
StopV2ChatResponseSchema,
PresignChatFilePostUrlSchema,
PresignChatFileGetUrlSchema
} from './api';
import { CreatePostPresignedUrlResultSchema } from '../../../../../service/common/s3/type';
import { z } from 'zod';
export const ChatControllerPath: OpenAPIPath = {
'/v2/chat/stop': {
post: {
summary: '停止 Agent 运行',
description: `优雅停止正在运行的 Agent, 会尝试等待当前节点结束后返回,最长 5s超过 5s 仍未结束,则会返回成功。
LLM 节点,流输出时会同时被终止,但 HTTP 请求节点这种可能长时间运行的,不会被终止。`,
tags: [TagsMap.chatController],
requestBody: {
content: {
'application/json': {
schema: StopV2ChatSchema
}
}
},
responses: {
200: {
description: '成功停止工作流',
content: {
'application/json': {
schema: StopV2ChatResponseSchema
}
}
}
}
}
},
'/core/chat/presignChatFilePostUrl': {
post: {
summary: '获取文件上传 URL',
description: '获取文件上传 URL',
tags: [TagsMap.chatController],
requestBody: {
content: {
'application/json': {
schema: PresignChatFilePostUrlSchema
}
}
},
responses: {
200: {
description: '成功上传对话文件预签名 URL',
content: {
'application/json': {
schema: CreatePostPresignedUrlResultSchema
}
}
}
}
}
},
'/core/chat/presignChatFileGetUrl': {
post: {
summary: '获取文件预览地址',
description: '获取文件预览地址',
tags: [TagsMap.chatController],
requestBody: {
content: {
'application/json': {
schema: PresignChatFileGetUrlSchema
}
}
},
responses: {
200: {
description: '成功获取对话文件预签名 URL',
content: {
'application/json': {
schema: z.string()
}
}
}
}
}
}
};

View File

@@ -3,89 +3,28 @@ import { ChatSettingPath } from './setting';
import { ChatFavouriteAppPath } from './favourite/index';
import { ChatFeedbackPath } from './feedback/index';
import { ChatHistoryPath } from './history/index';
import { z } from 'zod';
import { CreatePostPresignedUrlResultSchema } from '../../../../service/common/s3/type';
import {
PresignChatFileGetUrlSchema,
PresignChatFilePostUrlSchema,
StopV2ChatSchema,
StopV2ChatResponseSchema
} from './api';
import { GetRecentlyUsedAppsResponseSchema } from './api';
import { TagsMap } from '../../tag';
import { ChatControllerPath } from './controler';
export const ChatPath: OpenAPIPath = {
...ChatSettingPath,
...ChatFavouriteAppPath,
...ChatFeedbackPath,
...ChatHistoryPath,
...ChatControllerPath,
'/v2/chat/stop': {
post: {
summary: '停止 Agent 运行',
description: `优雅停止正在运行的 Agent, 会尝试等待当前节点结束后返回,最长 5s超过 5s 仍未结束,则会返回成功。
LLM 节点,流输出时会同时被终止,但 HTTP 请求节点这种可能长时间运行的,不会被终止。`,
'/core/chat/recentlyUsed': {
get: {
summary: '获取最近使用的应用',
description: '获取最近使用的应用',
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: StopV2ChatSchema
}
}
},
responses: {
200: {
description: '成功停止工作流',
description: '成功返回最近使用的应用',
content: {
'application/json': {
schema: StopV2ChatResponseSchema
}
}
}
}
}
},
'/core/chat/presignChatFilePostUrl': {
post: {
summary: '获取文件上传 URL',
description: '获取文件上传 URL',
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: PresignChatFilePostUrlSchema
}
}
},
responses: {
200: {
description: '成功上传对话文件预签名 URL',
content: {
'application/json': {
schema: CreatePostPresignedUrlResultSchema
}
}
}
}
}
},
'/core/chat/presignChatFileGetUrl': {
post: {
summary: '获取文件预览地址',
description: '获取文件预览地址',
tags: [TagsMap.chatPage],
requestBody: {
content: {
'application/json': {
schema: PresignChatFileGetUrlSchema
}
}
},
responses: {
200: {
description: '成功获取对话文件预签名 URL',
content: {
'application/json': {
schema: z.string()
schema: GetRecentlyUsedAppsResponseSchema
}
}
}

View File

@@ -26,7 +26,7 @@ export const openAPIDocument = createDocument({
'x-tagGroups': [
{
name: 'Agent 应用',
tags: [TagsMap.appLog]
tags: [TagsMap.appLog, TagsMap.publishChannel]
},
{
name: '对话管理',

View File

@@ -4,7 +4,8 @@ export const TagsMap = {
appLog: 'Agent 日志',
// Chat - home
chatPage: '对话页操作',
chatPage: '对话页',
chatController: '对话框操作',
chatHistory: '对话历史管理',
chatSetting: '门户页配置',
chatFeedback: '对话反馈',
@@ -13,6 +14,9 @@ export const TagsMap = {
pluginToolTag: '工具标签',
pluginTeam: '团队插件管理',
// Publish Channel
publishChannel: '发布渠道',
/* Support */
// Wallet
walletBill: '订单',

View File

@@ -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
>;

View File

@@ -5,5 +5,6 @@ export enum PublishChannelEnum {
feishu = 'feishu',
dingtalk = 'dingtalk',
wecom = 'wecom',
officialAccount = 'official_account'
officialAccount = 'official_account',
playground = 'playground'
}

View File

@@ -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>;