perf: update app usingtime code

This commit is contained in:
archer 2025-12-24 13:24:02 +08:00
parent d8d17031ae
commit 5fd854af64
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
20 changed files with 238 additions and 219 deletions

View File

@ -437,7 +437,7 @@ import {
setAgentRuntimeStop,
waitForWorkflowComplete
} from '@fastgpt/service/core/workflow/dispatch/workflowStatus';
import { StopV2ChatSchema, type StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/api';
import { StopV2ChatSchema, type StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/controler/api';
async function handler(req: NextApiRequest, res: NextApiResponse): Promise<StopV2ChatResponse> {
const { appId, chatId, outLinkAuthData } = StopV2ChatSchema.parse(req.body);
@ -527,7 +527,7 @@ LLM 节点,流输出时会同时被终止,但 HTTP 请求节点这种可能
```typescript
import { POST } from '@/web/common/api/request';
import type { StopV2ChatParams, StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/api';
import type { StopV2ChatParams, StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/controler/api';
/**
* 停止 v2 版本工作流运行

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

@ -4,7 +4,8 @@ export const TagsMap = {
appLog: 'Agent 日志',
// Chat - home
chatPage: '对话页操作',
chatPage: '对话页',
chatController: '对话框操作',
chatHistory: '对话历史管理',
chatSetting: '门户页配置',
chatFeedback: '对话反馈',

View File

@ -210,24 +210,23 @@ export const deleteAppsImmediate = async ({
await MongoAppRecord.deleteMany({ teamId, appId: { $in: appIds } });
};
export async function updateParentFoldersUpdateTime({
export const updateParentFoldersUpdateTime = async ({
parentId,
session
}: {
parentId?: string | null;
session?: ClientSession;
}): Promise<void> {
if (!parentId) return;
}) => {
while (true) {
if (!parentId) return;
const parentApp = await MongoApp.findById(parentId, 'parentId updateTime');
if (!parentApp) return;
const parentApp = await MongoApp.findById(parentId, 'parentId updateTime');
if (!parentApp) return;
parentApp.updateTime = new Date();
await parentApp.save({ session });
parentApp.updateTime = new Date();
await parentApp.save({ session });
// Recursively update parent folders
await updateParentFoldersUpdateTime({
parentId: parentApp.parentId,
session
});
}
// 递归删除
parentId = parentApp.parentId;
}
};

View File

@ -10,12 +10,3 @@ export const AppRecordSchemaZod = z.object({
// TypeScript types inferred from Zod schemas
export type AppRecordType = z.infer<typeof AppRecordSchemaZod>;
export const GetRecentlyUsedAppsResponseSchema = z.array(
z.object({
_id: z.string(),
name: z.string(),
avatar: z.string()
})
);
export type GetRecentlyUsedAppsResponseType = z.infer<typeof GetRecentlyUsedAppsResponseSchema>;

View File

@ -1,5 +1,5 @@
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { MongoAppRecord } from './schema';
import { addLog } from '../../../common/system/log';
export const recordAppUsage = async ({
appId,
@ -10,32 +10,18 @@ export const recordAppUsage = async ({
tmbId: string;
teamId: string;
}) => {
await mongoSessionRun(async (session) => {
await MongoAppRecord.updateOne(
{ tmbId, appId },
{
$set: {
teamId,
lastUsedTime: new Date()
}
},
{
upsert: true,
session
await MongoAppRecord.updateOne(
{ tmbId, appId },
{
$set: {
teamId,
lastUsedTime: new Date()
}
);
// 检查是否超过50条如果超过则删除最旧的一条
const count = await MongoAppRecord.countDocuments({ tmbId }, { session });
if (count > 50) {
await MongoAppRecord.deleteOne(
{ tmbId },
{
session,
sort: { lastUsedTime: 1 }
}
);
},
{
upsert: true
}
).catch((error) => {
addLog.error('recordAppUsage error', error);
});
};

View File

@ -19,7 +19,6 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useContextSelector } from 'use-context-selector';
import { ChatPageContext } from '@/web/core/chat/context/chatPageContext';
import { usePathname } from 'next/navigation';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type';
type Props = {
activeAppId: string;
@ -534,7 +533,7 @@ const ChatSlider = ({ activeAppId }: Props) => {
<MyBox flex={'1 0 0'} h={0} overflow={'overlay'} px={4} position={'relative'}>
{myApps.map((item) => (
<Flex
key={item._id}
key={item.appId}
py={2}
px={2}
mb={3}
@ -542,12 +541,12 @@ const ChatSlider = ({ activeAppId }: Props) => {
borderRadius={'md'}
alignItems={'center'}
fontSize={'sm'}
{...(pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && item._id === activeAppId
{...(pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && item.appId === activeAppId
? { bg: 'primary.100', color: 'primary.600' }
: {
_hover: { bg: 'primary.100' },
onClick: () =>
handlePaneChange(ChatSidebarPaneEnum.RECENTLY_USED_APPS, item._id)
handlePaneChange(ChatSidebarPaneEnum.RECENTLY_USED_APPS, item.appId)
})}
>
<Avatar src={item.avatar} w={'1.5rem'} borderRadius={'md'} />

View File

@ -14,6 +14,7 @@ import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { presignVariablesFileUrls } from '@fastgpt/service/core/chat/utils';
import { MongoAppRecord } from '@fastgpt/service/core/app/record/schema';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
async function handler(
req: NextApiRequest,
@ -82,15 +83,14 @@ async function handler(
};
} catch (error: any) {
if (error === AppErrEnum.unAuthApp) {
const { tmbId, teamId } = await authUserPer({
const { tmbId, teamId } = await authCert({
req,
authToken: true,
authApiKey: true
});
await MongoAppRecord.deleteMany({
await MongoAppRecord.deleteOne({
tmbId,
teamId,
appId
});
}

View File

@ -2,7 +2,7 @@ import type { ApiRequestProps } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import type { PresignChatFileGetUrlParams } from '@fastgpt/global/openapi/core/chat/api';
import type { PresignChatFileGetUrlParams } from '@fastgpt/global/openapi/core/chat/controler/api';
async function handler(req: ApiRequestProps<PresignChatFileGetUrlParams>): Promise<string> {
const { key, appId, outLinkAuthData } = req.body;

View File

@ -5,7 +5,7 @@ import { getS3ChatSource } from '@fastgpt/service/common/s3/sources/chat';
import { authChatCrud } from '@/service/support/permission/auth/chat';
import { authFrequencyLimit } from '@/service/common/frequencyLimit/api';
import { addSeconds } from 'date-fns';
import type { PresignChatFilePostUrlParams } from '@fastgpt/global/openapi/core/chat/api';
import type { PresignChatFilePostUrlParams } from '@fastgpt/global/openapi/core/chat/controler/api';
const authUploadLimit = (tmbId: string) => {
if (!global.feConfigs.uploadFileMaxAmount) return;

View File

@ -3,12 +3,12 @@ import { NextAPI } from '@/service/middleware/entry';
import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
import { MongoAppRecord } from '@fastgpt/service/core/app/record/schema';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/global/openapi/core/chat/api';
async function handler(
req: ApiRequestProps<{}, {}>,
_res: ApiResponseType<GetRecentlyUsedAppsResponseType>
) {
_res: ApiResponseType
): Promise<GetRecentlyUsedAppsResponseType> {
const { tmbId } = await authUserPer({
req,
authToken: true,
@ -34,7 +34,7 @@ async function handler(
.map((record) => appMap.get(String(record.appId)))
.filter((app) => app != null)
.map((app) => ({
_id: String(app._id),
appId: String(app._id),
name: app.name,
avatar: app.avatar
}));

View File

@ -382,13 +382,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
await saveChat(params);
}
setImmediate(async () => {
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
if (isOwnerUse && source === ChatSourceEnum.online) {
await recordAppUsage({
appId: String(app._id),
tmbId: String(tmbId),
teamId: String(teamId)
appId: app._id,
tmbId,
teamId
});
});
}
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);

View File

@ -384,13 +384,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
await saveChat(params);
}
setImmediate(async () => {
const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId);
if (isOwnerUse && source === ChatSourceEnum.online) {
await recordAppUsage({
appId: String(app._id),
tmbId: String(tmbId),
teamId: String(teamId)
appId: app._id,
tmbId,
teamId
});
});
}
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);

View File

@ -5,7 +5,10 @@ import {
setAgentRuntimeStop,
waitForWorkflowComplete
} from '@fastgpt/service/core/workflow/dispatch/workflowStatus';
import { StopV2ChatSchema, type StopV2ChatResponse } from '@fastgpt/global/openapi/core/chat/api';
import {
StopV2ChatSchema,
type StopV2ChatResponse
} from '@fastgpt/global/openapi/core/chat/controler/api';
async function handler(req: NextApiRequest, res: NextApiResponse): Promise<StopV2ChatResponse> {
const { appId, chatId, outLinkAuthData } = StopV2ChatSchema.parse(req.body);

View File

@ -5,7 +5,6 @@ import type { CreateAppBody } from '@/pages/api/core/app/create';
import type { ListAppBody } from '@/pages/api/core/app/list';
import type { getBasicInfoResponse } from '@/pages/api/core/app/getBasicInfo';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type';
/**
*
@ -15,9 +14,6 @@ export const getMyApps = (data?: ListAppBody) =>
maxQuantity: 1
});
export const getRecentlyUsedApps = () =>
GET<GetRecentlyUsedAppsResponseType>('/core/app/recentlyUsed');
/**
*
*/

View File

@ -24,7 +24,11 @@ import type {
UpdateFavouriteAppParamsType
} from '@fastgpt/global/openapi/core/chat/favourite/api';
import type { ChatFavouriteAppType } from '@fastgpt/global/core/chat/favouriteApp/type';
import type { StopV2ChatParams } from '@fastgpt/global/openapi/core/chat/api';
import type { StopV2ChatParams } from '@fastgpt/global/openapi/core/chat/controler/api';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/global/openapi/core/chat/api';
export const getRecentlyUsedApps = () =>
GET<GetRecentlyUsedAppsResponseType>('/core/chat/recentlyUsed');
/**
*

View File

@ -13,10 +13,10 @@ import { useRouter } from 'next/router';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { createContext } from 'use-context-selector';
import { useMemoEnhance } from '@fastgpt/web/hooks/useMemoEnhance';
import { getRecentlyUsedApps } from '@/web/core/app/api';
import { getRecentlyUsedApps } from '@/web/core/chat/api';
import { useUserStore } from '@/web/support/user/useUserStore';
import { useMount } from 'ahooks';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type';
import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/global/openapi/core/chat/api';
import type { UserType } from '@fastgpt/global/support/user/type';
export type ChatPageContextValue = {
@ -33,6 +33,7 @@ export type ChatPageContextValue = {
chatSettings: ChatSettingType | undefined;
refreshChatSetting: () => Promise<ChatSettingType | undefined>;
logos: { wideLogoUrl?: string; squareLogoUrl?: string };
// User & apps
isInitedUser: boolean;
userInfo: UserType | null;