From 284ca367aabca735c58c49199329e7affcc85508 Mon Sep 17 00:00:00 2001 From: heheer Date: Fri, 19 Dec 2025 11:33:49 +0800 Subject: [PATCH 1/6] app update time --- packages/service/core/app/controller.ts | 24 ++++++++++++++ packages/service/core/chat/saveChat.ts | 14 -------- projects/app/src/pages/api/core/app/create.ts | 6 ++++ .../pages/api/core/app/httpTools/update.ts | 7 ++++ .../src/pages/api/core/app/mcpTools/update.ts | 7 ++++ projects/app/src/pages/api/core/app/update.ts | 23 ++++++++++++- .../src/pages/api/core/app/version/publish.ts | 11 +++++++ .../app/src/pages/api/core/chat/chatTest.ts | 1 - .../app/src/pages/api/v1/chat/completions.ts | 2 -- .../app/src/pages/api/v2/chat/completions.ts | 2 -- projects/app/src/service/core/app/utils.ts | 1 - projects/app/src/service/support/mcp/utils.ts | 1 - test/cases/service/core/chat/saveChat.test.ts | 33 +------------------ 13 files changed, 78 insertions(+), 54 deletions(-) diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index fb07552a2..1facc88e2 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -204,3 +204,27 @@ export const deleteAppsImmediate = async ({ ).lean(); await Promise.all(evalJobs.map((evalJob) => removeEvaluationJob(evalJob._id))); }; + +export async function updateParentFoldersUpdateTime({ + parentId, + session +}: { + parentId?: string | null; + session?: ClientSession; +}): Promise { + if (!parentId) return; + + const parentApp = await MongoApp.findById(parentId).lean(); + if (!parentApp) return; + + // Only update if parent is a folder + if (AppFolderTypeList.includes(parentApp.type)) { + await MongoApp.findByIdAndUpdate(parentId, { updateTime: new Date() }, { session }); + } + + // Recursively update parent folders + await updateParentFoldersUpdateTime({ + parentId: parentApp.parentId, + session + }); +} diff --git a/packages/service/core/chat/saveChat.ts b/packages/service/core/chat/saveChat.ts index f427a40b1..a18773fa1 100644 --- a/packages/service/core/chat/saveChat.ts +++ b/packages/service/core/chat/saveChat.ts @@ -35,7 +35,6 @@ export type Props = { nodes: StoreNodeItemType[]; appChatConfig?: AppChatConfigType; variables?: Record; - isUpdateUseTime: boolean; newTitle: string; source: `${ChatSourceEnum}`; sourceName?: string; @@ -219,7 +218,6 @@ export async function saveChat(props: Props) { nodes, appChatConfig, variables, - isUpdateUseTime, newTitle, source, sourceName, @@ -393,18 +391,6 @@ export async function saveChat(props: Props) { } catch (error) { addLog.error('Push chat log error', error); } - - if (isUpdateUseTime) { - await MongoApp.updateOne( - { _id: appId }, - { - updateTime: new Date() - }, - { - ...writePrimary - } - ).catch(); - } } catch (error) { addLog.error(`update chat history error`, error); } diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index ea9265c5f..8246df691 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -33,6 +33,7 @@ import { isS3ObjectKey } from '@fastgpt/service/common/s3/utils'; import { MongoAppTemplate } from '@fastgpt/service/core/app/templates/templateSchema'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import path from 'node:path'; +import { updateParentFoldersUpdateTime } from '@fastgpt/service/core/app/controller'; export type CreateAppBody = { parentId?: ParentIdType; @@ -243,6 +244,11 @@ export const onCreateApp = async ({ await getS3AvatarSource().refreshAvatar(_avatar, undefined, session); + await updateParentFoldersUpdateTime({ + parentId, + session + }); + (async () => { addAuditLog({ tmbId, diff --git a/projects/app/src/pages/api/core/app/httpTools/update.ts b/projects/app/src/pages/api/core/app/httpTools/update.ts index 6ee3a5cc9..344bbf2f4 100644 --- a/projects/app/src/pages/api/core/app/httpTools/update.ts +++ b/projects/app/src/pages/api/core/app/httpTools/update.ts @@ -10,6 +10,7 @@ import { MongoApp } from '@fastgpt/service/core/app/schema'; import type { StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { storeSecretValue } from '@fastgpt/service/common/secret/utils'; import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; +import { updateParentFoldersUpdateTime } from '@fastgpt/service/core/app/controller'; export type UpdateHttpPluginBody = { appId: string; @@ -50,6 +51,12 @@ async function handler(req: ApiRequestProps, res: NextApiR }, { session } ); + + await updateParentFoldersUpdateTime({ + parentId: app.parentId, + session + }); + await MongoAppVersion.updateOne( { appId }, { diff --git a/projects/app/src/pages/api/core/app/mcpTools/update.ts b/projects/app/src/pages/api/core/app/mcpTools/update.ts index df0cca86f..555a3646d 100644 --- a/projects/app/src/pages/api/core/app/mcpTools/update.ts +++ b/projects/app/src/pages/api/core/app/mcpTools/update.ts @@ -10,6 +10,7 @@ import { getMCPToolSetRuntimeNode } from '@fastgpt/global/core/app/tool/mcpTool/ import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema'; import { type StoreSecretValueType } from '@fastgpt/global/common/secret/type'; import { storeSecretValue } from '@fastgpt/service/common/secret/utils'; +import { updateParentFoldersUpdateTime } from '@fastgpt/service/core/app/controller'; export type updateMCPToolsQuery = {}; @@ -51,6 +52,12 @@ async function handler( }, { session } ); + + await updateParentFoldersUpdateTime({ + parentId: app.parentId, + session + }); + await MongoAppVersion.updateOne( { appId }, { diff --git a/projects/app/src/pages/api/core/app/update.ts b/projects/app/src/pages/api/core/app/update.ts index 68a20455a..358045ec7 100644 --- a/projects/app/src/pages/api/core/app/update.ts +++ b/projects/app/src/pages/api/core/app/update.ts @@ -27,6 +27,7 @@ import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants'; import { getI18nAppType } from '@fastgpt/service/support/user/audit/util'; import { i18nT } from '@fastgpt/web/i18n/utils'; import { getS3AvatarSource } from '@fastgpt/service/common/s3/sources/avatar'; +import { updateParentFoldersUpdateTime } from '@fastgpt/service/core/app/controller'; export type AppUpdateQuery = { appId: string; @@ -117,7 +118,7 @@ async function handler(req: ApiRequestProps) { await getS3AvatarSource().refreshAvatar(avatar, app.avatar, session); - return MongoApp.findByIdAndUpdate( + const result = await MongoApp.findByIdAndUpdate( appId, { ...parseParentIdInMongo(parentId), @@ -137,6 +138,26 @@ async function handler(req: ApiRequestProps) { }, { session } ); + + if (isMove) { + // Update both old and new parent folders + await updateParentFoldersUpdateTime({ + parentId: app.parentId, + session + }); + await updateParentFoldersUpdateTime({ + parentId, + session + }); + } else { + // Update current parent folder + await updateParentFoldersUpdateTime({ + parentId: parentId || app.parentId, + session + }); + } + + return result; }; // Move diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts index 6d804a085..ce3dc632d 100644 --- a/projects/app/src/pages/api/core/app/version/publish.ts +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -13,6 +13,7 @@ import { addAuditLog } from '@fastgpt/service/support/user/audit/util'; import { AuditEventEnum } from '@fastgpt/global/support/user/audit/constants'; import { getI18nAppType } from '@fastgpt/service/support/user/audit/util'; import { i18nT } from '@fastgpt/web/i18n/utils'; +import { updateParentFoldersUpdateTime } from '@fastgpt/service/core/app/controller'; async function handler(req: ApiRequestProps, res: NextApiResponse) { const { appId } = req.query as { appId: string }; @@ -61,6 +62,11 @@ async function handler(req: ApiRequestProps, res: NextApiRe session } ); + + await updateParentFoldersUpdateTime({ + parentId: app.parentId, + session + }); }); addAuditLog({ @@ -122,6 +128,11 @@ async function handler(req: ApiRequestProps, res: NextApiRe session } ); + + await updateParentFoldersUpdateTime({ + parentId: app.parentId, + session + }); }); (async () => { diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index 8c934d397..c035395b1 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -248,7 +248,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { nodes, appChatConfig: chatConfig, variables: newVariables, - isUpdateUseTime: false, // owner update use time newTitle, source: ChatSourceEnum.test, userContent: userQuestion, diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 68e9387e3..2df434716 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -326,7 +326,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { })(); // save chat - const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId); const source = (() => { if (shareId) { return ChatSourceEnum.share; @@ -363,7 +362,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { nodes, appChatConfig: chatConfig, variables: newVariables, - isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time newTitle, shareId, outLinkUid: outLinkUserId, diff --git a/projects/app/src/pages/api/v2/chat/completions.ts b/projects/app/src/pages/api/v2/chat/completions.ts index 8cfde3e15..b1d14e471 100644 --- a/projects/app/src/pages/api/v2/chat/completions.ts +++ b/projects/app/src/pages/api/v2/chat/completions.ts @@ -328,7 +328,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { })(); // save chat - const isOwnerUse = !shareId && !spaceTeamId && String(tmbId) === String(app.tmbId); const source = (() => { if (shareId) { return ChatSourceEnum.share; @@ -365,7 +364,6 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { nodes, appChatConfig: chatConfig, variables: newVariables, - isUpdateUseTime: isOwnerUse && source === ChatSourceEnum.online, // owner update use time newTitle, shareId, outLinkUid: outLinkUserId, diff --git a/projects/app/src/service/core/app/utils.ts b/projects/app/src/service/core/app/utils.ts index e8f6685e5..cde645cab 100644 --- a/projects/app/src/service/core/app/utils.ts +++ b/projects/app/src/service/core/app/utils.ts @@ -101,7 +101,6 @@ export const getScheduleTriggerApp = async () => { nodes, appChatConfig: chatConfig, variables: {}, - isUpdateUseTime: false, // owner update use time newTitle: 'Cron Job', source: ChatSourceEnum.cronJob, userContent: { diff --git a/projects/app/src/service/support/mcp/utils.ts b/projects/app/src/service/support/mcp/utils.ts index 0eca67f80..fbf2645cd 100644 --- a/projects/app/src/service/support/mcp/utils.ts +++ b/projects/app/src/service/support/mcp/utils.ts @@ -257,7 +257,6 @@ export const callMcpServerTool = async ({ key, toolName, inputs }: toolCallProps nodes, appChatConfig: chatConfig, variables: newVariables, - isUpdateUseTime: false, // owner update use time newTitle, source: ChatSourceEnum.mcp, userContent: userQuestion, diff --git a/test/cases/service/core/chat/saveChat.test.ts b/test/cases/service/core/chat/saveChat.test.ts index 5fade7cf1..9c1308b93 100644 --- a/test/cases/service/core/chat/saveChat.test.ts +++ b/test/cases/service/core/chat/saveChat.test.ts @@ -31,7 +31,6 @@ const createMockProps = ( outputs: [] } ], - isUpdateUseTime: true, newTitle: 'Test Chat', source: 'online' as any, userContent: { @@ -228,7 +227,7 @@ describe('saveChat', () => { collectionId: 'collection-1', sourceId: 'source-1', sourceName: 'doc.pdf', - score: [{ type: 'embedding', value: 0.95, index: 0 }], + score: [{ type: 'embedding' as const, value: 0.95, index: 0 }], q: 'What is AI?', a: 'AI stands for Artificial Intelligence...', updateTime: new Date() @@ -283,36 +282,6 @@ describe('saveChat', () => { } }); - it('should update app use time when isUpdateUseTime is true', async () => { - const beforeTime = new Date(); - - const props = createMockProps( - { isUpdateUseTime: true }, - { appId: testAppId, teamId: testTeamId, tmbId: testTmbId } - ); - - await saveChat(props); - - const app = await MongoApp.findById(testAppId); - expect(app?.updateTime).toBeDefined(); - expect(app!.updateTime.getTime()).toBeGreaterThanOrEqual(beforeTime.getTime()); - }); - - it('should not update app use time when isUpdateUseTime is false', async () => { - const app = await MongoApp.findById(testAppId); - const originalUpdateTime = app!.updateTime; - - const props = createMockProps( - { isUpdateUseTime: false }, - { appId: testAppId, teamId: testTeamId, tmbId: testTmbId } - ); - - await saveChat(props); - - const updatedApp = await MongoApp.findById(testAppId); - expect(updatedApp!.updateTime.getTime()).toBe(originalUpdateTime.getTime()); - }); - it('should create chat data log with error count when response has error', async () => { const props = createMockProps( { From afd9e6c439904918ae8f9dcd79f8e13b5188a6b6 Mon Sep 17 00:00:00 2001 From: heheer Date: Fri, 19 Dec 2025 16:26:45 +0800 Subject: [PATCH 2/6] recent app --- packages/service/core/app/usage/schema.ts | 49 +++++++++++ packages/service/core/app/usage/utils.ts | 52 ++++++++++++ .../app/src/pageComponents/chat/useChat.ts | 2 +- projects/app/src/pages/api/core/app/list.ts | 12 +-- .../src/pages/api/core/app/recentlyUsed.ts | 82 +++++++++++++++++++ .../app/src/pages/api/v1/chat/completions.ts | 9 ++ .../app/src/pages/api/v2/chat/completions.ts | 9 ++ projects/app/src/web/core/app/api.ts | 5 +- 8 files changed, 204 insertions(+), 16 deletions(-) create mode 100644 packages/service/core/app/usage/schema.ts create mode 100644 packages/service/core/app/usage/utils.ts create mode 100644 projects/app/src/pages/api/core/app/recentlyUsed.ts diff --git a/packages/service/core/app/usage/schema.ts b/packages/service/core/app/usage/schema.ts new file mode 100644 index 000000000..10ffca1e2 --- /dev/null +++ b/packages/service/core/app/usage/schema.ts @@ -0,0 +1,49 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { getMongoModel, Schema } from '../../../common/mongo'; + +export const AppUsageCollectionName = 'app_usages'; + +const AppUsageSchema = new Schema( + { + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + appId: { + type: Schema.Types.ObjectId, + ref: 'apps', + required: true + }, + lastUsedTime: { + type: Date, + default: () => new Date() + } + }, + { + minimize: false, + timestamps: false + } +); + +AppUsageSchema.index({ tmbId: 1, lastUsedTime: -1 }); // 查询用户最近使用的应用 +AppUsageSchema.index({ tmbId: 1, appId: 1 }, { unique: true }); // 防止重复记录 +AppUsageSchema.index({ teamId: 1, appId: 1 }); // 用于清理权限失效的记录 + +export const MongoAppUsage = getMongoModel(AppUsageCollectionName, AppUsageSchema); + +export type AppUsageType = { + _id?: string; + tmbId: string; + teamId: string; + appId: string; + lastUsedTime: Date; +}; diff --git a/packages/service/core/app/usage/utils.ts b/packages/service/core/app/usage/utils.ts new file mode 100644 index 000000000..49dd87805 --- /dev/null +++ b/packages/service/core/app/usage/utils.ts @@ -0,0 +1,52 @@ +import { mongoSessionRun } from '../../../common/mongo/sessionRun'; +import { MongoAppUsage } from './schema'; + +export const recordAppUsage = async ({ + appId, + tmbId, + teamId +}: { + appId: string; + tmbId: string; + teamId: string; +}) => { + await mongoSessionRun(async (session) => { + await MongoAppUsage.findOneAndUpdate( + { tmbId, appId }, + { + $set: { + teamId, + lastUsedTime: new Date() + } + }, + { + upsert: true, + new: true, + session + } + ); + + // 保留最新的50条记录,删除超出限制的旧记录 + const threshold = await MongoAppUsage.findOne( + { tmbId }, + { lastUsedTime: 1 }, + { + session, + sort: { lastUsedTime: -1 }, + skip: 49, + lean: true + } + ); + + if (threshold) { + await MongoAppUsage.deleteMany( + { + tmbId, + _id: { $ne: threshold._id }, + lastUsedTime: { $lte: threshold.lastUsedTime } + }, + { session } + ); + } + }); +}; diff --git a/projects/app/src/pageComponents/chat/useChat.ts b/projects/app/src/pageComponents/chat/useChat.ts index 4e27f49cd..c470f498a 100644 --- a/projects/app/src/pageComponents/chat/useChat.ts +++ b/projects/app/src/pageComponents/chat/useChat.ts @@ -12,7 +12,7 @@ export const useChat = (appId: string) => { const [isInitedUser, setIsInitedUser] = useState(false); // get app list - const { data: myApps = [] } = useRequest2(() => getRecentlyUsedApps({ getRecentlyChat: true }), { + const { data: myApps = [] } = useRequest2(() => getRecentlyUsedApps(), { manual: false, errorToast: '', refreshDeps: [userInfo], diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index 949b86f63..65b1adeb8 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -23,7 +23,6 @@ import { sumPer } from '@fastgpt/global/support/permission/utils'; export type ListAppBody = { parentId?: ParentIdType; type?: AppTypeEnum | AppTypeEnum[]; - getRecentlyChat?: boolean; searchKey?: string; }; @@ -38,7 +37,7 @@ export type ListAppBody = { */ async function handler(req: ApiRequestProps): Promise { - const { parentId, type, getRecentlyChat, searchKey } = req.body; + const { parentId, type, searchKey } = req.body; // Auth user permission const [{ tmbId, teamId, permission: teamPer }] = await Promise.all([ @@ -94,14 +93,6 @@ async function handler(req: ApiRequestProps): Promise { - if (getRecentlyChat) { - return { - // get all chat app, excluding hidden apps and deleted apps - teamId, - type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.workflowTool] } - }; - } - // Filter apps by permission, if not owner, only get apps that I have permission to access const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } }; const appPerQuery = teamPer.isOwner @@ -153,7 +144,6 @@ async function handler(req: ApiRequestProps): Promise { - if (getRecentlyChat) return 15; if (searchKey) return 50; return; })(); diff --git a/projects/app/src/pages/api/core/app/recentlyUsed.ts b/projects/app/src/pages/api/core/app/recentlyUsed.ts new file mode 100644 index 000000000..214911997 --- /dev/null +++ b/projects/app/src/pages/api/core/app/recentlyUsed.ts @@ -0,0 +1,82 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; +import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { MongoAppUsage } from '@fastgpt/service/core/app/usage/schema'; +import { addSourceMember } from '@fastgpt/service/support/user/utils'; +import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import type { AppListItemType } from '@fastgpt/global/core/app/type'; + +export type GetRecentlyUsedAppsResponse = AppListItemType[]; + +async function handler( + req: ApiRequestProps<{}, {}>, + _res: ApiResponseType +): Promise { + const { tmbId, teamId } = await authUserPer({ + req, + authToken: true, + authApiKey: true + }); + + const recentUsages = await MongoAppUsage.find( + { tmbId }, + { appId: 1 }, + { sort: { lastUsedTime: -1 }, limit: 20 } + ).lean(); + + if (!recentUsages.length) return []; + + const appIds = recentUsages.map((usage) => usage.appId); + + // 并发检查权限 + const results = await Promise.allSettled( + appIds.map((appId) => + authApp({ req, authToken: true, authApiKey: true, appId, per: ReadPermissionVal }) + .then(({ app }) => ({ appId, app })) + .catch(() => ({ appId, app: null })) + ) + ); + + const validApps: { + appId: string; + app: NonNullable>['app']>; + }[] = []; + const invalidAppIds: string[] = []; + + for (const result of results) { + if (result.status === 'fulfilled') { + const { appId, app } = result.value; + if (app) { + validApps.push({ appId, app }); + } else { + invalidAppIds.push(appId); + } + } + } + + // 异步清理无效记录 + if (invalidAppIds.length) { + MongoAppUsage.deleteMany({ tmbId, teamId, appId: { $in: invalidAppIds } }).catch((err) => + console.error('Failed to clean invalid app usage records:', err) + ); + } + + return addSourceMember({ + list: validApps.map(({ app }) => ({ + _id: app._id, + parentId: app.parentId, + tmbId: app.tmbId, + name: app.name, + avatar: app.avatar, + intro: app.intro, + type: app.type, + updateTime: app.updateTime, + pluginData: app.pluginData, + permission: app.permission, + inheritPermission: app.inheritPermission + })) + }); +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 2df434716..64680ed55 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -27,6 +27,7 @@ import { } from '@fastgpt/service/core/chat/saveChat'; import { responseWrite } from '@fastgpt/service/common/response'; import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'; +import { recordAppUsage } from '@fastgpt/service/core/app/usage/utils'; import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools'; import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools'; import { authTeamSpaceToken } from '@/service/support/permission/auth/team'; @@ -381,6 +382,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { await saveChat(params); } + setImmediate(async () => { + await recordAppUsage({ + appId: String(app._id), + tmbId: String(tmbId), + teamId: String(teamId) + }); + }); + addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`); /* select fe response field */ diff --git a/projects/app/src/pages/api/v2/chat/completions.ts b/projects/app/src/pages/api/v2/chat/completions.ts index b1d14e471..c251a500e 100644 --- a/projects/app/src/pages/api/v2/chat/completions.ts +++ b/projects/app/src/pages/api/v2/chat/completions.ts @@ -27,6 +27,7 @@ import { } from '@fastgpt/service/core/chat/saveChat'; import { responseWrite } from '@fastgpt/service/common/response'; import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'; +import { recordAppUsage } from '@fastgpt/service/core/app/usage/utils'; import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools'; import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools'; import { authTeamSpaceToken } from '@/service/support/permission/auth/team'; @@ -383,6 +384,14 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { await saveChat(params); } + setImmediate(async () => { + await recordAppUsage({ + appId: String(app._id), + tmbId: String(tmbId), + teamId: String(teamId) + }); + }); + addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`); /* select fe response field */ diff --git a/projects/app/src/web/core/app/api.ts b/projects/app/src/web/core/app/api.ts index 7d2a795e3..cae04e228 100644 --- a/projects/app/src/web/core/app/api.ts +++ b/projects/app/src/web/core/app/api.ts @@ -14,10 +14,7 @@ export const getMyApps = (data?: ListAppBody) => maxQuantity: 1 }); -export const getRecentlyUsedApps = (data?: ListAppBody) => - POST('/core/app/list?t=0', data, { - maxQuantity: 1 - }); +export const getRecentlyUsedApps = () => GET('/core/app/recentlyUsed'); /** * 创建一个应用 From 56b0165f3d46e33f714575ffd3fa12e55e9e3223 Mon Sep 17 00:00:00 2001 From: heheer Date: Mon, 22 Dec 2025 17:26:47 +0800 Subject: [PATCH 3/6] fix --- packages/service/core/app/controller.ts | 11 +- packages/service/core/app/record/schema.ts | 45 +++++++ packages/service/core/app/record/type.ts | 69 ++++++++++ .../core/app/{usage => record}/utils.ts | 20 +-- packages/service/core/app/usage/schema.ts | 49 -------- .../chat/ChatWindow/AppChatWindow.tsx | 15 ++- .../chat/ChatWindow/HomeChatWindow.tsx | 7 +- .../app/src/pageComponents/chat/useChat.ts | 5 +- .../src/pages/api/core/app/recentlyUsed.ts | 63 ++++------ .../src/pages/api/core/app/version/publish.ts | 14 +-- projects/app/src/pages/api/core/chat/init.ts | 119 ++++++++++-------- .../app/src/pages/api/v1/chat/completions.ts | 2 +- .../app/src/pages/api/v2/chat/completions.ts | 2 +- projects/app/src/pages/chat/index.tsx | 20 ++- 14 files changed, 266 insertions(+), 175 deletions(-) create mode 100644 packages/service/core/app/record/schema.ts create mode 100644 packages/service/core/app/record/type.ts rename packages/service/core/app/{usage => record}/utils.ts (61%) delete mode 100644 packages/service/core/app/usage/schema.ts diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index 1facc88e2..a7e6e8f0f 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -27,6 +27,8 @@ import { getS3ChatSource } from '../../common/s3/sources/chat'; import { MongoAppChatLog } from './logs/chatLogsSchema'; import { MongoAppRegistration } from '../../support/appRegistration/schema'; import { MongoMcpKey } from '../../support/mcp/schema'; +import { type ClientSession } from '../../common/mongo'; +import { MongoAppRecord } from './record/schema'; export const beforeUpdateAppFormat = ({ nodes }: { nodes?: StoreNodeItemType[] }) => { if (!nodes) return; @@ -181,6 +183,8 @@ export const deleteAppDataProcessor = async ({ await MongoAppRegistration.deleteMany({ appId }); // 删除应用从MCP key apps数组中移除 await MongoMcpKey.updateMany({ teamId, 'apps.appId': appId }, { $pull: { apps: { appId } } }); + // 删除应用使用记录 + await MongoAppRecord.deleteMany({ appId }); // 删除应用本身 await MongoApp.deleteOne({ _id: appId }); @@ -214,13 +218,10 @@ export async function updateParentFoldersUpdateTime({ }): Promise { if (!parentId) return; - const parentApp = await MongoApp.findById(parentId).lean(); + const parentApp = await MongoApp.findById(parentId, 'parentId'); if (!parentApp) return; - // Only update if parent is a folder - if (AppFolderTypeList.includes(parentApp.type)) { - await MongoApp.findByIdAndUpdate(parentId, { updateTime: new Date() }, { session }); - } + await MongoApp.findByIdAndUpdate(parentId, { updateTime: new Date() }, { session }); // Recursively update parent folders await updateParentFoldersUpdateTime({ diff --git a/packages/service/core/app/record/schema.ts b/packages/service/core/app/record/schema.ts new file mode 100644 index 000000000..1a04a5236 --- /dev/null +++ b/packages/service/core/app/record/schema.ts @@ -0,0 +1,45 @@ +import { + TeamCollectionName, + TeamMemberCollectionName +} from '@fastgpt/global/support/user/team/constant'; +import { getMongoModel, Schema } from '../../../common/mongo'; +import { AppCollectionName } from '../schema'; +import type { AppRecordType } from './type'; + +export const AppRecordCollectionName = 'app_records'; + +const AppRecordSchema = new Schema( + { + tmbId: { + type: Schema.Types.ObjectId, + ref: TeamMemberCollectionName, + required: true + }, + teamId: { + type: Schema.Types.ObjectId, + ref: TeamCollectionName, + required: true + }, + appId: { + type: Schema.Types.ObjectId, + ref: AppCollectionName, + required: true + }, + lastUsedTime: { + type: Date, + default: () => new Date() + } + }, + { + timestamps: false + } +); + +AppRecordSchema.index({ tmbId: 1, lastUsedTime: -1 }); // 查询用户最近使用的应用 +AppRecordSchema.index({ tmbId: 1, appId: 1 }, { unique: true }); // 防止重复记录 +AppRecordSchema.index({ teamId: 1, appId: 1 }); // 用于清理权限失效的记录 + +export const MongoAppRecord = getMongoModel( + AppRecordCollectionName, + AppRecordSchema +); diff --git a/packages/service/core/app/record/type.ts b/packages/service/core/app/record/type.ts new file mode 100644 index 000000000..85303b130 --- /dev/null +++ b/packages/service/core/app/record/type.ts @@ -0,0 +1,69 @@ +import { z } from 'zod'; + +// Zod schemas +export const AppRecordSchemaZod = z.object({ + _id: z.string().optional(), + tmbId: z.string(), + teamId: z.string(), + appId: z.string(), + lastUsedTime: z.date() +}); + +// 创建应用记录时的 schema(不包含 _id) +export const CreateAppRecordSchemaZod = AppRecordSchemaZod.omit({ + _id: true +}); + +// 更新应用记录时的 schema(部分字段可选) +export const UpdateAppRecordSchemaZod = AppRecordSchemaZod.partial().omit({ + _id: true, + tmbId: true, + teamId: true, + appId: true +}); + +// 查询参数的 schema +export const AppRecordQuerySchemaZod = z.object({ + tmbId: z.string().optional(), + teamId: z.string().optional(), + appId: z.string().optional(), + lastUsedTime: z + .union([ + z.date(), + z.object({ + gte: z.date().optional(), + lte: z.date().optional(), + gt: z.date().optional(), + lt: z.date().optional() + }) + ]) + .optional() +}); + +// TypeScript types inferred from Zod schemas +export type AppRecordType = z.infer; +export type CreateAppRecordType = z.infer; +export type UpdateAppRecordType = z.infer; +export type AppRecordQueryType = z.infer; + +// 兼容旧版本的类型定义(保持向后兼容) +export type { + AppRecordType as AppRecordSchemaType, + CreateAppRecordType as AppRecordCreateType, + UpdateAppRecordType as AppRecordUpdateType +}; + +// 应用记录统计类型 +export const AppRecordStatsSchemaZod = z.object({ + totalRecords: z.number().min(0), + uniqueApps: z.number().min(0), + mostUsedApp: z + .object({ + appId: z.string(), + usageCount: z.number().min(0) + }) + .optional(), + lastUsedTime: z.date().optional() +}); + +export type AppRecordStatsType = z.infer; diff --git a/packages/service/core/app/usage/utils.ts b/packages/service/core/app/record/utils.ts similarity index 61% rename from packages/service/core/app/usage/utils.ts rename to packages/service/core/app/record/utils.ts index 49dd87805..f82d1ba3e 100644 --- a/packages/service/core/app/usage/utils.ts +++ b/packages/service/core/app/record/utils.ts @@ -1,5 +1,5 @@ import { mongoSessionRun } from '../../../common/mongo/sessionRun'; -import { MongoAppUsage } from './schema'; +import { MongoAppRecord } from './schema'; export const recordAppUsage = async ({ appId, @@ -11,7 +11,7 @@ export const recordAppUsage = async ({ teamId: string; }) => { await mongoSessionRun(async (session) => { - await MongoAppUsage.findOneAndUpdate( + await MongoAppRecord.findOneAndUpdate( { tmbId, appId }, { $set: { @@ -26,24 +26,24 @@ export const recordAppUsage = async ({ } ); - // 保留最新的50条记录,删除超出限制的旧记录 - const threshold = await MongoAppUsage.findOne( + const records = await MongoAppRecord.find( { tmbId }, - { lastUsedTime: 1 }, + { _id: 1 }, { session, sort: { lastUsedTime: -1 }, - skip: 49, lean: true } ); - if (threshold) { - await MongoAppUsage.deleteMany( + if (records.length > 50) { + const toDeleteRecords = records.slice(50); + const toDeleteIds = toDeleteRecords.map((record) => record._id); + + await MongoAppRecord.deleteMany( { tmbId, - _id: { $ne: threshold._id }, - lastUsedTime: { $lte: threshold.lastUsedTime } + _id: { $in: toDeleteIds } }, { session } ); diff --git a/packages/service/core/app/usage/schema.ts b/packages/service/core/app/usage/schema.ts deleted file mode 100644 index 10ffca1e2..000000000 --- a/packages/service/core/app/usage/schema.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - TeamCollectionName, - TeamMemberCollectionName -} from '@fastgpt/global/support/user/team/constant'; -import { getMongoModel, Schema } from '../../../common/mongo'; - -export const AppUsageCollectionName = 'app_usages'; - -const AppUsageSchema = new Schema( - { - tmbId: { - type: Schema.Types.ObjectId, - ref: TeamMemberCollectionName, - required: true - }, - teamId: { - type: Schema.Types.ObjectId, - ref: TeamCollectionName, - required: true - }, - appId: { - type: Schema.Types.ObjectId, - ref: 'apps', - required: true - }, - lastUsedTime: { - type: Date, - default: () => new Date() - } - }, - { - minimize: false, - timestamps: false - } -); - -AppUsageSchema.index({ tmbId: 1, lastUsedTime: -1 }); // 查询用户最近使用的应用 -AppUsageSchema.index({ tmbId: 1, appId: 1 }, { unique: true }); // 防止重复记录 -AppUsageSchema.index({ teamId: 1, appId: 1 }); // 用于清理权限失效的记录 - -export const MongoAppUsage = getMongoModel(AppUsageCollectionName, AppUsageSchema); - -export type AppUsageType = { - _id?: string; - tmbId: string; - teamId: string; - appId: string; - lastUsedTime: Date; -}; diff --git a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx index e0251ab60..2522ec4c2 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx @@ -32,9 +32,10 @@ const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPlu type Props = { myApps: AppListItemType[]; + refreshRecentlyUsed?: () => void; }; -const AppChatWindow = ({ myApps }: Props) => { +const AppChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { const { userInfo } = useUserStore(); const { chatId, appId, outLinkAuthData } = useChatStore(); @@ -122,9 +123,19 @@ const AppChatWindow = ({ myApps }: Props) => { title: newTitle })); + refreshRecentlyUsed?.(); + return { responseText, isNewChat: forbidLoadChat.current }; }, - [appId, chatId, onUpdateHistoryTitle, setChatBoxData, forbidLoadChat, isShowCite] + [ + appId, + chatId, + onUpdateHistoryTitle, + setChatBoxData, + forbidLoadChat, + isShowCite, + refreshRecentlyUsed + ] ); return ( diff --git a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx index 062b176a8..7b8fb59f5 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx @@ -51,6 +51,7 @@ import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; type Props = { myApps: AppListItemType[]; + refreshRecentlyUsed?: () => void; }; const defaultFileSelectConfig: AppFileSelectConfigType = { @@ -68,7 +69,7 @@ const defaultWhisperConfig: AppWhisperConfigType = { autoTTSResponse: false }; -const HomeChatWindow = ({ myApps }: Props) => { +const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { const { t } = useTranslation(); const { isPc } = useSystem(); @@ -232,6 +233,8 @@ const HomeChatWindow = ({ myApps }: Props) => { title: newTitle })); + refreshRecentlyUsed?.(); + return { responseText, isNewChat: forbidLoadChat.current }; } @@ -281,6 +284,8 @@ const HomeChatWindow = ({ myApps }: Props) => { title: newTitle })); + refreshRecentlyUsed?.(); + return { responseText, isNewChat: forbidLoadChat.current }; } ); diff --git a/projects/app/src/pageComponents/chat/useChat.ts b/projects/app/src/pageComponents/chat/useChat.ts index c470f498a..88afe1b23 100644 --- a/projects/app/src/pageComponents/chat/useChat.ts +++ b/projects/app/src/pageComponents/chat/useChat.ts @@ -12,7 +12,7 @@ export const useChat = (appId: string) => { const [isInitedUser, setIsInitedUser] = useState(false); // get app list - const { data: myApps = [] } = useRequest2(() => getRecentlyUsedApps(), { + const { data: myApps = [], refresh } = useRequest2(() => getRecentlyUsedApps(), { manual: false, errorToast: '', refreshDeps: [userInfo], @@ -43,6 +43,7 @@ export const useChat = (appId: string) => { return { isInitedUser, userInfo, - myApps + myApps, + refreshRecentlyUsed: refresh }; }; diff --git a/projects/app/src/pages/api/core/app/recentlyUsed.ts b/projects/app/src/pages/api/core/app/recentlyUsed.ts index 214911997..a47d4d064 100644 --- a/projects/app/src/pages/api/core/app/recentlyUsed.ts +++ b/projects/app/src/pages/api/core/app/recentlyUsed.ts @@ -1,11 +1,11 @@ import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import { NextAPI } from '@/service/middleware/entry'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; -import { MongoAppUsage } from '@fastgpt/service/core/app/usage/schema'; +import { MongoAppRecord } from '@fastgpt/service/core/app/record/schema'; +import { MongoApp } from '@fastgpt/service/core/app/schema'; import { addSourceMember } from '@fastgpt/service/support/user/utils'; -import { authApp } from '@fastgpt/service/support/permission/app/auth'; import type { AppListItemType } from '@fastgpt/global/core/app/type'; +import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; export type GetRecentlyUsedAppsResponse = AppListItemType[]; @@ -13,57 +13,37 @@ async function handler( req: ApiRequestProps<{}, {}>, _res: ApiResponseType ): Promise { - const { tmbId, teamId } = await authUserPer({ + const { tmbId } = await authUserPer({ req, authToken: true, authApiKey: true }); - const recentUsages = await MongoAppUsage.find( + const recentRecords = await MongoAppRecord.find( { tmbId }, { appId: 1 }, { sort: { lastUsedTime: -1 }, limit: 20 } ).lean(); - if (!recentUsages.length) return []; + if (!recentRecords.length) return []; - const appIds = recentUsages.map((usage) => usage.appId); + const appIds = recentRecords.map((record) => record.appId); - // 并发检查权限 - const results = await Promise.allSettled( - appIds.map((appId) => - authApp({ req, authToken: true, authApiKey: true, appId, per: ReadPermissionVal }) - .then(({ app }) => ({ appId, app })) - .catch(() => ({ appId, app: null })) - ) - ); + const apps = await MongoApp.find( + { + _id: { $in: appIds }, + deleteTime: null + }, + '_id parentId tmbId name avatar intro type updateTime pluginData inheritPermission' + ).lean(); - const validApps: { - appId: string; - app: NonNullable>['app']>; - }[] = []; - const invalidAppIds: string[] = []; - - for (const result of results) { - if (result.status === 'fulfilled') { - const { appId, app } = result.value; - if (app) { - validApps.push({ appId, app }); - } else { - invalidAppIds.push(appId); - } - } - } - - // 异步清理无效记录 - if (invalidAppIds.length) { - MongoAppUsage.deleteMany({ tmbId, teamId, appId: { $in: invalidAppIds } }).catch((err) => - console.error('Failed to clean invalid app usage records:', err) - ); - } + const appMap = new Map(apps.map((app) => [String(app._id), app])); + const sortedApps = recentRecords + .map((record) => appMap.get(String(record.appId))) + .filter((app) => app != null); return addSourceMember({ - list: validApps.map(({ app }) => ({ + list: sortedApps.map((app) => ({ _id: app._id, parentId: app.parentId, tmbId: app.tmbId, @@ -73,7 +53,10 @@ async function handler( type: app.type, updateTime: app.updateTime, pluginData: app.pluginData, - permission: app.permission, + permission: new AppPermission({ + role: 0, + isOwner: String(app.tmbId) === String(tmbId) + }), inheritPermission: app.inheritPermission })) }); diff --git a/projects/app/src/pages/api/core/app/version/publish.ts b/projects/app/src/pages/api/core/app/version/publish.ts index ce3dc632d..d7a1e3873 100644 --- a/projects/app/src/pages/api/core/app/version/publish.ts +++ b/projects/app/src/pages/api/core/app/version/publish.ts @@ -30,6 +30,10 @@ async function handler(req: ApiRequestProps, res: NextApiRe nodes }); + await updateParentFoldersUpdateTime({ + parentId: app.parentId + }); + if (autoSave) { await mongoSessionRun(async (session) => { await MongoAppVersion.updateOne( @@ -62,11 +66,6 @@ async function handler(req: ApiRequestProps, res: NextApiRe session } ); - - await updateParentFoldersUpdateTime({ - parentId: app.parentId, - session - }); }); addAuditLog({ @@ -128,11 +127,6 @@ async function handler(req: ApiRequestProps, res: NextApiRe session } ); - - await updateParentFoldersUpdateTime({ - parentId: app.parentId, - session - }); }); (async () => { diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts index efd1fc3cf..840786906 100644 --- a/projects/app/src/pages/api/core/chat/init.ts +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -1,6 +1,7 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { jsonRes } from '@fastgpt/service/common/response'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; +import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; import { getGuideModule, getAppChatConfig } from '@fastgpt/global/core/workflow/utils'; import { getChatModelNameListByModules } from '@/service/core/app/workflow'; import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d'; @@ -11,6 +12,8 @@ import { NextAPI } from '@/service/middleware/entry'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; 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'; async function handler( req: NextApiRequest, @@ -25,57 +28,75 @@ async function handler( }); } - // auth app permission - const [{ app, tmbId }, chat] = await Promise.all([ - authApp({ - req, - authToken: true, - authApiKey: true, - appId, - per: ReadPermissionVal - }), - chatId ? MongoChat.findOne({ appId, chatId }) : undefined - ]); - - // auth chat permission - if (chat && !app.permission.hasReadChatLogPer && String(tmbId) !== String(chat?.tmbId)) { - return Promise.reject(ChatErrEnum.unAuthChat); - } - - // get app and history - const { nodes, chatConfig } = await getAppLatestVersion(app._id, app); - const pluginInputs = - chat?.pluginInputs ?? - nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? - []; - - const variables = await presignVariablesFileUrls({ - variables: chat?.variables, - variableConfig: chat?.variableList - }); - - return { - chatId, - appId, - title: chat?.title, - userAvatar: undefined, - variables, - app: { - chatConfig: getAppChatConfig({ - chatConfig, - systemConfigNode: getGuideModule(nodes), - storeVariables: chat?.variableList, - storeWelcomeText: chat?.welcomeText, - isPublicFetch: false + try { + // auth app permission + const [{ app, tmbId }, chat] = await Promise.all([ + authApp({ + req, + authToken: true, + authApiKey: true, + appId, + per: ReadPermissionVal }), - chatModels: getChatModelNameListByModules(nodes), - name: app.name, - avatar: app.avatar, - intro: app.intro, - type: app.type, - pluginInputs + chatId ? MongoChat.findOne({ appId, chatId }) : undefined + ]); + + // auth chat permission + if (chat && !app.permission.hasReadChatLogPer && String(tmbId) !== String(chat?.tmbId)) { + return Promise.reject(ChatErrEnum.unAuthChat); } - }; + + // get app and history + const { nodes, chatConfig } = await getAppLatestVersion(app._id, app); + const pluginInputs = + chat?.pluginInputs ?? + nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? + []; + + const variables = await presignVariablesFileUrls({ + variables: chat?.variables, + variableConfig: chat?.variableList + }); + + return { + chatId, + appId, + title: chat?.title, + userAvatar: undefined, + variables, + app: { + chatConfig: getAppChatConfig({ + chatConfig, + systemConfigNode: getGuideModule(nodes), + storeVariables: chat?.variableList, + storeWelcomeText: chat?.welcomeText, + isPublicFetch: false + }), + chatModels: getChatModelNameListByModules(nodes), + name: app.name, + avatar: app.avatar, + intro: app.intro, + type: app.type, + pluginInputs + } + }; + } catch (error: any) { + if (error === AppErrEnum.unAuthApp) { + const { tmbId, teamId } = await authUserPer({ + req, + authToken: true, + authApiKey: true + }); + + await MongoAppRecord.deleteMany({ + tmbId, + teamId, + appId + }); + } + + return Promise.reject(error); + } } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 64680ed55..a42ea345b 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -27,7 +27,7 @@ import { } from '@fastgpt/service/core/chat/saveChat'; import { responseWrite } from '@fastgpt/service/common/response'; import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'; -import { recordAppUsage } from '@fastgpt/service/core/app/usage/utils'; +import { recordAppUsage } from '@fastgpt/service/core/app/record/utils'; import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools'; import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools'; import { authTeamSpaceToken } from '@/service/support/permission/auth/team'; diff --git a/projects/app/src/pages/api/v2/chat/completions.ts b/projects/app/src/pages/api/v2/chat/completions.ts index c251a500e..3a36e7a8b 100644 --- a/projects/app/src/pages/api/v2/chat/completions.ts +++ b/projects/app/src/pages/api/v2/chat/completions.ts @@ -27,7 +27,7 @@ import { } from '@fastgpt/service/core/chat/saveChat'; import { responseWrite } from '@fastgpt/service/common/response'; import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink'; -import { recordAppUsage } from '@fastgpt/service/core/app/usage/utils'; +import { recordAppUsage } from '@fastgpt/service/core/app/record/utils'; import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools'; import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools'; import { authTeamSpaceToken } from '@/service/support/permission/auth/team'; diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 536b4d19d..d634e3f6c 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -33,7 +33,13 @@ 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 Chat = ({ + myApps, + refreshRecentlyUsed +}: { + myApps: AppListItemType[]; + refreshRecentlyUsed: () => void; +}) => { const { isPc } = useSystem(); const { appId } = useChatStore(); @@ -62,7 +68,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { {(!datasetCiteData || isPc) && ( {/* home chat window */} - {pane === ChatSidebarPaneEnum.HOME && } + {pane === ChatSidebarPaneEnum.HOME && ( + + )} {/* favourite apps */} {pane === ChatSidebarPaneEnum.FAVORITE_APPS && } @@ -71,7 +79,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { {pane === ChatSidebarPaneEnum.TEAM_APPS && } {/* recently used apps chat window */} - {pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && } + {pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && ( + + )} {/* setting */} {pane === ChatSidebarPaneEnum.SETTING && } @@ -103,7 +113,7 @@ const Render = (props: { const { chatId } = useChatStore(); const { setUserInfo } = useUserStore(); const { feConfigs } = useSystemStore(); - const { isInitedUser, userInfo, myApps } = useChat(appId); + const { isInitedUser, userInfo, myApps, refreshRecentlyUsed } = useChat(appId); const chatHistoryProviderParams = useMemo( () => ({ appId, source: ChatSourceEnum.online }), @@ -154,7 +164,7 @@ const Render = (props: { isShowFullText={props.showFullText} > - + From 3b09092ede4453849a28e3840be71e9ff0d0ad16 Mon Sep 17 00:00:00 2001 From: heheer Date: Mon, 22 Dec 2025 17:38:22 +0800 Subject: [PATCH 4/6] type --- packages/service/core/app/record/type.ts | 56 ------------------------ 1 file changed, 56 deletions(-) diff --git a/packages/service/core/app/record/type.ts b/packages/service/core/app/record/type.ts index 85303b130..bab7ee087 100644 --- a/packages/service/core/app/record/type.ts +++ b/packages/service/core/app/record/type.ts @@ -9,61 +9,5 @@ export const AppRecordSchemaZod = z.object({ lastUsedTime: z.date() }); -// 创建应用记录时的 schema(不包含 _id) -export const CreateAppRecordSchemaZod = AppRecordSchemaZod.omit({ - _id: true -}); - -// 更新应用记录时的 schema(部分字段可选) -export const UpdateAppRecordSchemaZod = AppRecordSchemaZod.partial().omit({ - _id: true, - tmbId: true, - teamId: true, - appId: true -}); - -// 查询参数的 schema -export const AppRecordQuerySchemaZod = z.object({ - tmbId: z.string().optional(), - teamId: z.string().optional(), - appId: z.string().optional(), - lastUsedTime: z - .union([ - z.date(), - z.object({ - gte: z.date().optional(), - lte: z.date().optional(), - gt: z.date().optional(), - lt: z.date().optional() - }) - ]) - .optional() -}); - // TypeScript types inferred from Zod schemas export type AppRecordType = z.infer; -export type CreateAppRecordType = z.infer; -export type UpdateAppRecordType = z.infer; -export type AppRecordQueryType = z.infer; - -// 兼容旧版本的类型定义(保持向后兼容) -export type { - AppRecordType as AppRecordSchemaType, - CreateAppRecordType as AppRecordCreateType, - UpdateAppRecordType as AppRecordUpdateType -}; - -// 应用记录统计类型 -export const AppRecordStatsSchemaZod = z.object({ - totalRecords: z.number().min(0), - uniqueApps: z.number().min(0), - mostUsedApp: z - .object({ - appId: z.string(), - usageCount: z.number().min(0) - }) - .optional(), - lastUsedTime: z.date().optional() -}); - -export type AppRecordStatsType = z.infer; From 884b38e9e1cb4ba00d75ea0b9f573616cc3726d7 Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 23 Dec 2025 11:13:07 +0800 Subject: [PATCH 5/6] fix --- packages/service/core/app/controller.ts | 10 ++-- packages/service/core/app/record/type.ts | 12 ++++- packages/service/core/app/record/utils.ts | 29 ++++-------- .../src/pageComponents/chat/ChatHeader.tsx | 8 ++-- .../chat/ChatWindow/AppChatWindow.tsx | 3 +- .../chat/ChatWindow/HomeChatWindow.tsx | 9 ++-- .../src/pageComponents/chat/slider/index.tsx | 3 +- .../src/pages/api/core/app/recentlyUsed.ts | 46 +++++-------------- projects/app/src/pages/chat/index.tsx | 3 +- projects/app/src/web/core/app/api.ts | 4 +- 10 files changed, 53 insertions(+), 74 deletions(-) diff --git a/packages/service/core/app/controller.ts b/packages/service/core/app/controller.ts index a7e6e8f0f..0a2402183 100644 --- a/packages/service/core/app/controller.ts +++ b/packages/service/core/app/controller.ts @@ -183,8 +183,6 @@ export const deleteAppDataProcessor = async ({ await MongoAppRegistration.deleteMany({ appId }); // 删除应用从MCP key apps数组中移除 await MongoMcpKey.updateMany({ teamId, 'apps.appId': appId }, { $pull: { apps: { appId } } }); - // 删除应用使用记录 - await MongoAppRecord.deleteMany({ appId }); // 删除应用本身 await MongoApp.deleteOne({ _id: appId }); @@ -207,6 +205,9 @@ export const deleteAppsImmediate = async ({ '_id' ).lean(); await Promise.all(evalJobs.map((evalJob) => removeEvaluationJob(evalJob._id))); + + // Remove app record + await MongoAppRecord.deleteMany({ teamId, appId: { $in: appIds } }); }; export async function updateParentFoldersUpdateTime({ @@ -218,10 +219,11 @@ export async function updateParentFoldersUpdateTime({ }): Promise { if (!parentId) return; - const parentApp = await MongoApp.findById(parentId, 'parentId'); + const parentApp = await MongoApp.findById(parentId, 'parentId updateTime'); if (!parentApp) return; - await MongoApp.findByIdAndUpdate(parentId, { updateTime: new Date() }, { session }); + parentApp.updateTime = new Date(); + await parentApp.save({ session }); // Recursively update parent folders await updateParentFoldersUpdateTime({ diff --git a/packages/service/core/app/record/type.ts b/packages/service/core/app/record/type.ts index bab7ee087..127e6f44e 100644 --- a/packages/service/core/app/record/type.ts +++ b/packages/service/core/app/record/type.ts @@ -1,8 +1,7 @@ import { z } from 'zod'; -// Zod schemas export const AppRecordSchemaZod = z.object({ - _id: z.string().optional(), + _id: z.string(), tmbId: z.string(), teamId: z.string(), appId: z.string(), @@ -11,3 +10,12 @@ export const AppRecordSchemaZod = z.object({ // TypeScript types inferred from Zod schemas export type AppRecordType = z.infer; + +export const GetRecentlyUsedAppsResponseSchema = z.array( + z.object({ + _id: z.string(), + name: z.string(), + avatar: z.string() + }) +); +export type GetRecentlyUsedAppsResponseType = z.infer; diff --git a/packages/service/core/app/record/utils.ts b/packages/service/core/app/record/utils.ts index f82d1ba3e..c6ff3ee8e 100644 --- a/packages/service/core/app/record/utils.ts +++ b/packages/service/core/app/record/utils.ts @@ -11,7 +11,7 @@ export const recordAppUsage = async ({ teamId: string; }) => { await mongoSessionRun(async (session) => { - await MongoAppRecord.findOneAndUpdate( + await MongoAppRecord.updateOne( { tmbId, appId }, { $set: { @@ -21,31 +21,20 @@ export const recordAppUsage = async ({ }, { upsert: true, - new: true, session } ); - const records = await MongoAppRecord.find( - { tmbId }, - { _id: 1 }, - { - session, - sort: { lastUsedTime: -1 }, - lean: true - } - ); + // 检查是否超过50条,如果超过则删除最旧的一条 + const count = await MongoAppRecord.countDocuments({ tmbId }, { session }); - if (records.length > 50) { - const toDeleteRecords = records.slice(50); - const toDeleteIds = toDeleteRecords.map((record) => record._id); - - await MongoAppRecord.deleteMany( + if (count > 50) { + await MongoAppRecord.deleteOne( + { tmbId }, { - tmbId, - _id: { $in: toDeleteIds } - }, - { session } + session, + sort: { lastUsedTime: 1 } + } ); } }); diff --git a/projects/app/src/pageComponents/chat/ChatHeader.tsx b/projects/app/src/pageComponents/chat/ChatHeader.tsx index 4a37a7dc4..4bda278a8 100644 --- a/projects/app/src/pageComponents/chat/ChatHeader.tsx +++ b/projects/app/src/pageComponents/chat/ChatHeader.tsx @@ -14,7 +14,6 @@ import { AppFolderTypeList, AppTypeEnum } from '@fastgpt/global/core/app/constan import { useSystem } from '@fastgpt/web/hooks/useSystem'; import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import { useRouter } from 'next/router'; -import { type AppListItemType } from '@fastgpt/global/core/app/type'; import { type GetResourceFolderListProps, type GetResourceListItemResponse @@ -34,6 +33,7 @@ import { usePathname } from 'next/navigation'; import type { ChatSettingType } from '@fastgpt/global/core/chat/setting/type'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; const ChatHeader = ({ history, @@ -50,7 +50,7 @@ const ChatHeader = ({ history: ChatItemType[]; showHistory?: boolean; - apps?: AppListItemType[]; + apps?: GetRecentlyUsedAppsResponseType; totalRecordsCount: number; reserveSpace?: boolean; }) => { @@ -120,7 +120,7 @@ const MobileDrawer = ({ }: { onCloseDrawer: () => void; appId: string; - apps?: AppListItemType[]; + apps?: GetRecentlyUsedAppsResponseType; }) => { enum TabEnum { recently = 'recently', @@ -253,7 +253,7 @@ const MobileHeader = ({ showHistory?: boolean; avatar: string; name: string; - apps?: AppListItemType[]; + apps?: GetRecentlyUsedAppsResponseType; appId: string; }) => { const router = useRouter(); diff --git a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx index 2522ec4c2..829b8ff29 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx @@ -27,11 +27,12 @@ import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobil import dynamic from 'next/dynamic'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox')); type Props = { - myApps: AppListItemType[]; + myApps: GetRecentlyUsedAppsResponseType; refreshRecentlyUsed?: () => void; }; diff --git a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx index 7b8fb59f5..d9b3b3dfa 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx @@ -37,20 +37,17 @@ import { getToolPreviewNode } from '@/web/core/app/api/tool'; import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; import { getWebLLMModel } from '@/web/common/system/utils'; import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; -import type { - AppFileSelectConfigType, - AppListItemType, - AppWhisperConfigType -} from '@fastgpt/global/core/app/type'; +import type { AppFileSelectConfigType, AppWhisperConfigType } from '@fastgpt/global/core/app/type'; import ChatHeader from '@/pageComponents/chat/ChatHeader'; import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext'; import { ChatSidebarPaneEnum } from '../constants'; import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; type Props = { - myApps: AppListItemType[]; + myApps: GetRecentlyUsedAppsResponseType; refreshRecentlyUsed?: () => void; }; diff --git a/projects/app/src/pageComponents/chat/slider/index.tsx b/projects/app/src/pageComponents/chat/slider/index.tsx index d5837f146..a0175bfe6 100644 --- a/projects/app/src/pageComponents/chat/slider/index.tsx +++ b/projects/app/src/pageComponents/chat/slider/index.tsx @@ -19,10 +19,11 @@ import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useContextSelector } from 'use-context-selector'; import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; import { usePathname } from 'next/navigation'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; type Props = { activeAppId: string; - apps: AppListItemType[]; + apps: GetRecentlyUsedAppsResponseType; }; const MotionBox = motion(Box); diff --git a/projects/app/src/pages/api/core/app/recentlyUsed.ts b/projects/app/src/pages/api/core/app/recentlyUsed.ts index a47d4d064..495a1f6b9 100644 --- a/projects/app/src/pages/api/core/app/recentlyUsed.ts +++ b/projects/app/src/pages/api/core/app/recentlyUsed.ts @@ -3,16 +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 { addSourceMember } from '@fastgpt/service/support/user/utils'; -import type { AppListItemType } from '@fastgpt/global/core/app/type'; -import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; - -export type GetRecentlyUsedAppsResponse = AppListItemType[]; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; async function handler( req: ApiRequestProps<{}, {}>, - _res: ApiResponseType -): Promise { + _res: ApiResponseType +) { const { tmbId } = await authUserPer({ req, authToken: true, @@ -27,39 +23,21 @@ async function handler( if (!recentRecords.length) return []; - const appIds = recentRecords.map((record) => record.appId); - const apps = await MongoApp.find( - { - _id: { $in: appIds }, - deleteTime: null - }, - '_id parentId tmbId name avatar intro type updateTime pluginData inheritPermission' + { _id: { $in: recentRecords.map((record) => record.appId) } }, + '_id name avatar' ).lean(); const appMap = new Map(apps.map((app) => [String(app._id), app])); - const sortedApps = recentRecords - .map((record) => appMap.get(String(record.appId))) - .filter((app) => app != null); - return addSourceMember({ - list: sortedApps.map((app) => ({ - _id: app._id, - parentId: app.parentId, - tmbId: app.tmbId, + return recentRecords + .map((record) => appMap.get(String(record.appId))) + .filter((app) => app != null) + .map((app) => ({ + _id: String(app._id), name: app.name, - avatar: app.avatar, - intro: app.intro, - type: app.type, - updateTime: app.updateTime, - pluginData: app.pluginData, - permission: new AppPermission({ - role: 0, - isOwner: String(app.tmbId) === String(tmbId) - }), - inheritPermission: app.inheritPermission - })) - }); + avatar: app.avatar + })); } export default NextAPI(handler); diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index d634e3f6c..15c94c5e6 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -32,12 +32,13 @@ 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'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; const Chat = ({ myApps, refreshRecentlyUsed }: { - myApps: AppListItemType[]; + myApps: GetRecentlyUsedAppsResponseType; refreshRecentlyUsed: () => void; }) => { const { isPc } = useSystem(); diff --git a/projects/app/src/web/core/app/api.ts b/projects/app/src/web/core/app/api.ts index cae04e228..e925daa3b 100644 --- a/projects/app/src/web/core/app/api.ts +++ b/projects/app/src/web/core/app/api.ts @@ -5,6 +5,7 @@ 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'; /** * 获取应用列表 @@ -14,7 +15,8 @@ export const getMyApps = (data?: ListAppBody) => maxQuantity: 1 }); -export const getRecentlyUsedApps = () => GET('/core/app/recentlyUsed'); +export const getRecentlyUsedApps = () => + GET('/core/app/recentlyUsed'); /** * 创建一个应用 From d8d17031ae524eaa004b1f8644b1d434dc860198 Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 23 Dec 2025 11:13:32 +0800 Subject: [PATCH 6/6] context --- .../chat/ChatFavouriteApp/index.tsx | 10 +- .../src/pageComponents/chat/ChatHeader.tsx | 33 ++----- .../chat/ChatSetting/DataDashboard.tsx | 4 +- .../FavouriteAppSetting/TagManageModal.tsx | 6 +- .../ChatSetting/FavouriteAppSetting/index.tsx | 14 +-- .../ChatSetting/HomepageSetting/index.tsx | 6 +- .../chat/ChatSetting/LogDetails.tsx | 4 +- .../pageComponents/chat/ChatSetting/index.tsx | 6 +- .../pageComponents/chat/ChatTeamApp/List.tsx | 4 +- .../pageComponents/chat/ChatTeamApp/index.tsx | 4 +- .../chat/ChatWindow/AppChatWindow.tsx | 21 ++--- .../chat/ChatWindow/HomeChatWindow.tsx | 24 ++--- .../chat/slider/ChatSliderFooter.tsx | 6 +- .../chat/slider/ChatSliderHeader.tsx | 8 +- .../src/pageComponents/chat/slider/index.tsx | 42 ++++----- .../app/src/pageComponents/chat/useChat.ts | 49 ---------- projects/app/src/pages/chat/index.tsx | 76 +++++++-------- ...SettingContext.tsx => chatPageContext.tsx} | 93 ++++++++++++++++--- 18 files changed, 194 insertions(+), 216 deletions(-) delete mode 100644 projects/app/src/pageComponents/chat/useChat.ts rename projects/app/src/web/core/chat/context/{chatSettingContext.tsx => chatPageContext.tsx} (62%) diff --git a/projects/app/src/pageComponents/chat/ChatFavouriteApp/index.tsx b/projects/app/src/pageComponents/chat/ChatFavouriteApp/index.tsx index 36c109a7b..8e8b78241 100644 --- a/projects/app/src/pageComponents/chat/ChatFavouriteApp/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatFavouriteApp/index.tsx @@ -18,7 +18,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; import { useContextSelector } from 'use-context-selector'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { useMemo } from 'react'; import Avatar from '@fastgpt/web/components/common/Avatar'; import { ChatSettingTabOptionEnum, ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; @@ -41,11 +41,11 @@ const ChatFavouriteApp = () => { const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); - const wideLogoUrl = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.wideLogoUrl); - const homeTabTitle = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.homeTabTitle); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); + const wideLogoUrl = useContextSelector(ChatPageContext, (v) => v.chatSettings?.wideLogoUrl); + const homeTabTitle = useContextSelector(ChatPageContext, (v) => v.chatSettings?.homeTabTitle); - const tags = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.favouriteTags || []); + const tags = useContextSelector(ChatPageContext, (v) => v.chatSettings?.favouriteTags || []); const tagCache = useMemo(() => { return tags.reduce( (acc, tag) => { diff --git a/projects/app/src/pageComponents/chat/ChatHeader.tsx b/projects/app/src/pageComponents/chat/ChatHeader.tsx index 4bda278a8..b54dff79c 100644 --- a/projects/app/src/pageComponents/chat/ChatHeader.tsx +++ b/projects/app/src/pageComponents/chat/ChatHeader.tsx @@ -23,7 +23,7 @@ import SelectOneResource from '@/components/common/folder/SelectOneResource'; import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; import VariablePopover from '@/components/core/chat/ChatContainer/components/VariablePopover'; import { useCopyData } from '@fastgpt/web/hooks/useCopyData'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { ChatSidebarPaneEnum, DEFAULT_LOGO_BANNER_COLLAPSED_URL @@ -33,12 +33,10 @@ import { usePathname } from 'next/navigation'; import type { ChatSettingType } from '@fastgpt/global/core/chat/setting/type'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; -import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; const ChatHeader = ({ history, showHistory, - apps, totalRecordsCount, pane, @@ -50,18 +48,15 @@ const ChatHeader = ({ history: ChatItemType[]; showHistory?: boolean; - apps?: GetRecentlyUsedAppsResponseType; totalRecordsCount: number; reserveSpace?: boolean; }) => { const { t } = useTranslation(); const { isPc } = useSystem(); - const pathname = usePathname(); const { source } = useChatStore(); const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); const isVariableVisible = useContextSelector(ChatItemContext, (v) => v.isVariableVisible); - const isPlugin = chatData.app.type === AppTypeEnum.workflowTool; const isShare = source === 'share'; const chatType = isShare ? ChatTypeEnum.share : ChatTypeEnum.chat; @@ -87,7 +82,6 @@ const ChatHeader = ({ ) : ( void; - appId: string; - apps?: GetRecentlyUsedAppsResponseType; -}) => { +const MobileDrawer = ({ onCloseDrawer, appId }: { onCloseDrawer: () => void; appId: string }) => { enum TabEnum { recently = 'recently', app = 'app' @@ -129,6 +115,7 @@ const MobileDrawer = ({ const { t } = useTranslation(); const { setChatId } = useChatStore(); + const myApps = useContextSelector(ChatPageContext, (v) => v.myApps); const [currentTab, setCurrentTab] = useState(TabEnum.recently); @@ -143,7 +130,7 @@ const MobileDrawer = ({ ); }, []); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); const onclickApp = (id: string) => { handlePaneChange(ChatSidebarPaneEnum.RECENTLY_USED_APPS, id); @@ -201,8 +188,8 @@ const MobileDrawer = ({ {/* history */} {currentTab === TabEnum.recently && ( - {Array.isArray(apps) && - apps.map((item) => ( + {Array.isArray(myApps) && + myApps.map((item) => ( { const router = useRouter(); @@ -290,9 +275,7 @@ const MobileHeader = ({ - {isOpenDrawer && !isShareChat && ( - - )} + {isOpenDrawer && !isShareChat && } ); }; diff --git a/projects/app/src/pageComponents/chat/ChatSetting/DataDashboard.tsx b/projects/app/src/pageComponents/chat/ChatSetting/DataDashboard.tsx index d9e4ecca3..9cb47a951 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/DataDashboard.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/DataDashboard.tsx @@ -1,5 +1,5 @@ import LogChart from '@/pageComponents/app/detail/Logs/LogChart'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { Flex } from '@chakra-ui/react'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import type { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker'; @@ -13,7 +13,7 @@ type Props = { }; const LogDetails = ({ Header }: Props) => { - const appId = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.appId || ''); + const appId = useContextSelector(ChatPageContext, (v) => v.chatSettings?.appId || ''); const [dateRange, setDateRange] = useState({ from: new Date(addDays(new Date(), -6).setHours(0, 0, 0, 0)), diff --git a/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/TagManageModal.tsx b/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/TagManageModal.tsx index 56e7cd9a3..2192f636a 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/TagManageModal.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/TagManageModal.tsx @@ -1,4 +1,4 @@ -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { AddIcon } from '@chakra-ui/icons'; import { Box, @@ -377,10 +377,10 @@ type Props = { const TagManageModal = ({ onClose, onRefresh }: Props) => { const { t } = useTranslation(); - const refreshChatSetting = useContextSelector(ChatSettingContext, (v) => v.refreshChatSetting); + const refreshChatSetting = useContextSelector(ChatPageContext, (v) => v.refreshChatSetting); // get tags from db - const tags = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.favouriteTags || []); + const tags = useContextSelector(ChatPageContext, (v) => v.chatSettings?.favouriteTags || []); // local editable tags list const [localTags, setLocalTags] = useState(tags); diff --git a/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/index.tsx b/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/index.tsx index 6e11dd166..3262bd666 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/FavouriteAppSetting/index.tsx @@ -1,20 +1,12 @@ -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { Button, - ButtonGroup, Flex, HStack, IconButton, Input, InputGroup, InputLeftElement, - Table, - TableContainer, - Tbody, - Td, - Th, - Thead, - Tr, useDisclosure } from '@chakra-ui/react'; import MySelect from '@fastgpt/web/components/common/MySelect'; @@ -68,7 +60,7 @@ const FavouriteAppSetting = ({ Header }: Props) => { const searchAppTagValue = watchSearchValue('tag'); // apps' tags options - const tagOptions = useContextSelector(ChatSettingContext, (v) => { + const tagOptions = useContextSelector(ChatPageContext, (v) => { const tags = v.chatSettings?.favouriteTags || []; return [ { label: t('chat:setting.favourite.category_all'), value: '' }, @@ -76,7 +68,7 @@ const FavouriteAppSetting = ({ Header }: Props) => { ]; }); // app's tags cache map - const tagMap = useContextSelector(ChatSettingContext, (v) => + const tagMap = useContextSelector(ChatPageContext, (v) => (v.chatSettings?.favouriteTags || []).reduce>( (acc, tag) => { acc[tag.id] = { ...tag }; diff --git a/projects/app/src/pageComponents/chat/ChatSetting/HomepageSetting/index.tsx b/projects/app/src/pageComponents/chat/ChatSetting/HomepageSetting/index.tsx index b224d69bc..bdafa29c6 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/HomepageSetting/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/HomepageSetting/index.tsx @@ -23,7 +23,7 @@ import Avatar from '@fastgpt/web/components/common/Avatar'; import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; import { useMount } from 'ahooks'; import { useContextSelector } from 'use-context-selector'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { DEFAULT_LOGO_BANNER_COLLAPSED_URL, DEFAULT_LOGO_BANNER_URL @@ -46,8 +46,8 @@ const HomepageSetting = ({ Header, onDiagramShow }: Props) => { const { t } = useTranslation(); const { feConfigs } = useSystemStore(); - const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); - const refreshChatSetting = useContextSelector(ChatSettingContext, (v) => v.refreshChatSetting); + const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings); + const refreshChatSetting = useContextSelector(ChatPageContext, (v) => v.refreshChatSetting); const chatSettings2Form = useCallback( (data?: ChatSettingType) => { diff --git a/projects/app/src/pageComponents/chat/ChatSetting/LogDetails.tsx b/projects/app/src/pageComponents/chat/ChatSetting/LogDetails.tsx index c7da09305..9c3bac4b5 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/LogDetails.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/LogDetails.tsx @@ -1,5 +1,5 @@ import LogTable from '@/pageComponents/app/detail/Logs/LogTable'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { Flex } from '@chakra-ui/react'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; import type { DateRangeType } from '@fastgpt/web/components/common/DateRangePicker'; @@ -16,7 +16,7 @@ type Props = { const chatSourceValues = Object.values(ChatSourceEnum); const LogDetails = ({ Header }: Props) => { - const appId = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.appId || ''); + const appId = useContextSelector(ChatPageContext, (v) => v.chatSettings?.appId || ''); const [dateRange, setDateRange] = useState({ from: new Date(addDays(new Date(), -6).setHours(0, 0, 0, 0)), diff --git a/projects/app/src/pageComponents/chat/ChatSetting/index.tsx b/projects/app/src/pageComponents/chat/ChatSetting/index.tsx index 9587874f3..521c3dd00 100644 --- a/projects/app/src/pageComponents/chat/ChatSetting/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatSetting/index.tsx @@ -9,7 +9,7 @@ import { useContextSelector } from 'use-context-selector'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import NextHead from '@/components/common/NextHead'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; import { useTranslation } from 'react-i18next'; import { useMount } from 'ahooks'; @@ -43,8 +43,8 @@ const ChatSetting = () => { ); const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider); - const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); const handleTabChange = useCallback( (tab: ChatSettingTabOptionEnum) => { diff --git a/projects/app/src/pageComponents/chat/ChatTeamApp/List.tsx b/projects/app/src/pageComponents/chat/ChatTeamApp/List.tsx index 98fb90c69..f6d2c90d5 100644 --- a/projects/app/src/pageComponents/chat/ChatTeamApp/List.tsx +++ b/projects/app/src/pageComponents/chat/ChatTeamApp/List.tsx @@ -15,7 +15,7 @@ import AppTypeTag from '@/pageComponents/chat/ChatTeamApp/TypeTag'; import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import UserBox from '@fastgpt/web/components/common/UserBox'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; const List = ({ appType }: { appType: AppTypeEnum | 'all' }) => { @@ -33,7 +33,7 @@ const List = ({ appType }: { appType: AppTypeEnum | 'all' }) => { ].includes(app.type) ) ); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); return ( <> diff --git a/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx b/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx index 709d0d23b..d688c5d23 100644 --- a/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx +++ b/projects/app/src/pageComponents/chat/ChatTeamApp/index.tsx @@ -13,7 +13,7 @@ import SearchInput from '@fastgpt/web/components/common/Input/SearchInput'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import NextHead from '@/components/common/NextHead'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; @@ -29,7 +29,7 @@ const MyApps = () => { (v) => v ); - const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); + const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings); const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider); diff --git a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx index 829b8ff29..2beed8662 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/AppChatWindow.tsx @@ -7,7 +7,6 @@ import SideBar from '@/components/SideBar'; import { ChatContext } from '@/web/core/chat/context/chatContext'; import { useContextSelector } from 'use-context-selector'; import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; -import { type AppListItemType } from '@fastgpt/global/core/app/type'; import { ChatTypeEnum } from '@/components/core/chat/ChatContainer/ChatBox/constants'; import { useCallback } from 'react'; import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type'; @@ -20,23 +19,17 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getInitChatInfo } from '@/web/core/chat/api'; import { useUserStore } from '@/web/support/user/useUserStore'; import NextHead from '@/components/common/NextHead'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { ChatSidebarPaneEnum } from '../constants'; import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; import dynamic from 'next/dynamic'; import { getNanoid } from '@fastgpt/global/common/string/tools'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; -import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; const CustomPluginRunBox = dynamic(() => import('@/pageComponents/chat/CustomPluginRunBox')); -type Props = { - myApps: GetRecentlyUsedAppsResponseType; - refreshRecentlyUsed?: () => void; -}; - -const AppChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { +const AppChatWindow = () => { const { userInfo } = useUserStore(); const { chatId, appId, outLinkAuthData } = useChatStore(); @@ -57,9 +50,10 @@ const AppChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); - const chatSettings = useContextSelector(ChatSettingContext, (v) => v.chatSettings); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); + const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); + const refreshRecentlyUsed = useContextSelector(ChatPageContext, (v) => v.refreshRecentlyUsed); const { loading } = useRequest2( async () => { @@ -124,7 +118,7 @@ const AppChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { title: newTitle })); - refreshRecentlyUsed?.(); + refreshRecentlyUsed(); return { responseText, isNewChat: forbidLoadChat.current }; }, @@ -170,7 +164,6 @@ const AppChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { pane={pane} chatSettings={chatSettings} showHistory - apps={myApps} history={chatRecords} totalRecordsCount={totalRecordsCount} /> diff --git a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx index d9b3b3dfa..131e72da3 100644 --- a/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx +++ b/projects/app/src/pageComponents/chat/ChatWindow/HomeChatWindow.tsx @@ -36,7 +36,7 @@ import { getDefaultAppForm } from '@fastgpt/global/core/app/utils'; import { getToolPreviewNode } from '@/web/core/app/api/tool'; import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node'; import { getWebLLMModel } from '@/web/common/system/utils'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import type { AppFileSelectConfigType, AppWhisperConfigType } from '@fastgpt/global/core/app/type'; import ChatHeader from '@/pageComponents/chat/ChatHeader'; import { ChatRecordContext } from '@/web/core/chat/context/chatRecordContext'; @@ -44,12 +44,6 @@ import { ChatSidebarPaneEnum } from '../constants'; import ChatHistorySidebar from '@/pageComponents/chat/slider/ChatSliderSidebar'; import ChatSliderMobileDrawer from '@/pageComponents/chat/slider/ChatSliderMobileDrawer'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; -import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; - -type Props = { - myApps: GetRecentlyUsedAppsResponseType; - refreshRecentlyUsed?: () => void; -}; const defaultFileSelectConfig: AppFileSelectConfigType = { maxFiles: 20, @@ -66,7 +60,7 @@ const defaultWhisperConfig: AppWhisperConfigType = { autoTTSResponse: false }; -const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { +const HomeChatWindow = () => { const { t } = useTranslation(); const { isPc } = useSystem(); @@ -84,10 +78,11 @@ const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { 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); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); - const homeAppId = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.appId || ''); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); + const chatSettings = useContextSelector(ChatPageContext, (v) => v.chatSettings); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); + const homeAppId = useContextSelector(ChatPageContext, (v) => v.chatSettings?.appId || ''); + const refreshRecentlyUsed = useContextSelector(ChatPageContext, (v) => v.refreshRecentlyUsed); const chatRecords = useContextSelector(ChatRecordContext, (v) => v.chatRecords); const totalRecordsCount = useContextSelector(ChatRecordContext, (v) => v.totalRecordsCount); @@ -230,7 +225,7 @@ const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { title: newTitle })); - refreshRecentlyUsed?.(); + refreshRecentlyUsed(); return { responseText, isNewChat: forbidLoadChat.current }; } @@ -281,7 +276,7 @@ const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { title: newTitle })); - refreshRecentlyUsed?.(); + refreshRecentlyUsed(); return { responseText, isNewChat: forbidLoadChat.current }; } @@ -451,7 +446,6 @@ const HomeChatWindow = ({ myApps, refreshRecentlyUsed }: Props) => { pane={pane} chatSettings={chatSettings} showHistory - apps={myApps} history={chatRecords} totalRecordsCount={totalRecordsCount} /> diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx index ceadb24d2..0464de8f0 100644 --- a/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderFooter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; import { useContextSelector } from 'use-context-selector'; import { ChatContext } from '@/web/core/chat/context/chatContext'; @@ -15,8 +15,8 @@ const ChatSliderFooter = () => { const { feConfigs } = useSystemStore(); const onCloseSlider = useContextSelector(ChatContext, (v) => v.onCloseSlider); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); const isAdmin = !!userInfo?.team.permission.hasManagePer; const isSettingPane = pane === ChatSidebarPaneEnum.SETTING; diff --git a/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx b/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx index 11fb7b672..4d924f16c 100644 --- a/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx +++ b/projects/app/src/pageComponents/chat/slider/ChatSliderHeader.tsx @@ -1,6 +1,6 @@ import { GridItem, Grid } from '@chakra-ui/react'; import React from 'react'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext } from '@/web/core/chat/context/chatPageContext'; import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; import { useContextSelector } from 'use-context-selector'; import { ChatContext } from '@/web/core/chat/context/chatContext'; @@ -24,9 +24,9 @@ const ChatSliderHeader = ({ title, banner }: Props) => { const { isPc } = useSystem(); const { setChatId } = useChatStore(); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); - const enableHome = useContextSelector(ChatSettingContext, (v) => v.chatSettings?.enableHome); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); + const enableHome = useContextSelector(ChatPageContext, (v) => v.chatSettings?.enableHome); const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name); const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar); diff --git a/projects/app/src/pageComponents/chat/slider/index.tsx b/projects/app/src/pageComponents/chat/slider/index.tsx index a0175bfe6..5240427a3 100644 --- a/projects/app/src/pageComponents/chat/slider/index.tsx +++ b/projects/app/src/pageComponents/chat/slider/index.tsx @@ -17,13 +17,12 @@ import { } from '@/pageComponents/chat/constants'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useContextSelector } from 'use-context-selector'; -import { ChatSettingContext } from '@/web/core/chat/context/chatSettingContext'; +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; - apps: GetRecentlyUsedAppsResponseType; }; const MotionBox = motion(Box); @@ -149,13 +148,13 @@ const AnimatedText: React.FC = ({ show, children, className, ); const LogoSection = () => { - const isCollapsed = useContextSelector(ChatSettingContext, (v) => v.collapse === 1); - const logos = useContextSelector(ChatSettingContext, (v) => v.logos); + const isCollapsed = useContextSelector(ChatPageContext, (v) => v.collapse === 1); + const logos = useContextSelector(ChatPageContext, (v) => v.logos); const isHomeActive = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.pane === ChatSidebarPaneEnum.HOME ); - const onTriggerCollapse = useContextSelector(ChatSettingContext, (v) => v.onTriggerCollapse); + const onTriggerCollapse = useContextSelector(ChatPageContext, (v) => v.onTriggerCollapse); const wideLogoSrc = logos.wideLogoUrl; const squareLogoSrc = logos.squareLogoUrl; @@ -257,24 +256,24 @@ const NavigationSection = () => { const { feConfigs } = useSystemStore(); const isEnableHome = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.chatSettings?.enableHome ?? true ); - const isCollapsed = useContextSelector(ChatSettingContext, (v) => v.collapse === 1); - const onTriggerCollapse = useContextSelector(ChatSettingContext, (v) => v.onTriggerCollapse); + const isCollapsed = useContextSelector(ChatPageContext, (v) => v.collapse === 1); + const onTriggerCollapse = useContextSelector(ChatPageContext, (v) => v.onTriggerCollapse); const isHomeActive = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.pane === ChatSidebarPaneEnum.HOME ); const isTeamAppsActive = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.pane === ChatSidebarPaneEnum.TEAM_APPS ); const isFavouriteAppsActive = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.pane === ChatSidebarPaneEnum.FAVORITE_APPS ); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); return ( @@ -366,12 +365,12 @@ const BottomSection = () => { const isAdmin = !!userInfo?.team.permission.hasManagePer; const isShare = pathname === '/chat/share'; - const isCollapsed = useContextSelector(ChatSettingContext, (v) => v.collapse === 1); + const isCollapsed = useContextSelector(ChatPageContext, (v) => v.collapse === 1); const isSettingActive = useContextSelector( - ChatSettingContext, + ChatPageContext, (v) => v.pane === ChatSidebarPaneEnum.SETTING ); - const onSettingClick = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const onSettingClick = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); return ( @@ -486,13 +485,14 @@ const BottomSection = () => { ); }; -const ChatSlider = ({ apps, activeAppId }: Props) => { +const ChatSlider = ({ activeAppId }: Props) => { const { t } = useTranslation(); - const isCollapsed = useContextSelector(ChatSettingContext, (v) => v.collapse === 1); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); + const isCollapsed = useContextSelector(ChatPageContext, (v) => v.collapse === 1); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); + const myApps = useContextSelector(ChatPageContext, (v) => v.myApps); - const handlePaneChange = useContextSelector(ChatSettingContext, (v) => v.handlePaneChange); + const handlePaneChange = useContextSelector(ChatPageContext, (v) => v.handlePaneChange); return ( { - {apps.map((item) => ( + {myApps.map((item) => ( { - const { setSource, setAppId } = useChatStore(); - const { userInfo, initUserInfo } = useUserStore(); - - const [isInitedUser, setIsInitedUser] = useState(false); - - // get app list - const { data: myApps = [], refresh } = useRequest2(() => getRecentlyUsedApps(), { - manual: false, - errorToast: '', - refreshDeps: [userInfo], - pollingInterval: 30000 - }); - - // initialize user info - useMount(async () => { - // ensure store has current appId before setting source (avoids fallback to lastChatAppId) - if (appId) setAppId(appId); - try { - await initUserInfo(); - } catch (error) { - console.log('User not logged in:', error); - } finally { - setSource('online'); - setIsInitedUser(true); - } - }); - - // sync appId to store as soon as route/appId changes - useEffect(() => { - if (appId) { - setAppId(appId); - } - }, [appId, setAppId, userInfo]); - - return { - isInitedUser, - userInfo, - myApps, - refreshRecentlyUsed: refresh - }; -}; diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index 15c94c5e6..20ef86762 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -8,7 +8,6 @@ import { serviceSideProps } from '@/web/common/i18n/utils'; import { ChatSidebarPaneEnum } from '@/pageComponents/chat/constants'; import { GetChatTypeEnum } from '@/global/core/chat/constants'; import ChatContextProvider from '@/web/core/chat/context/chatContext'; -import { type AppListItemType } from '@fastgpt/global/core/app/type'; import { useContextSelector } from 'use-context-selector'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { ChatSourceEnum } from '@fastgpt/global/core/chat/constants'; @@ -18,13 +17,9 @@ import ChatQuoteList from '@/pageComponents/chat/ChatQuoteList'; import LoginModal from '@/pageComponents/login/LoginModal'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import ChatSetting from '@/pageComponents/chat/ChatSetting'; -import { useChat } from '@/pageComponents/chat/useChat'; import AppChatWindow from '@/pageComponents/chat/ChatWindow/AppChatWindow'; import HomeChatWindow from '@/pageComponents/chat/ChatWindow/HomeChatWindow'; -import { - ChatSettingContext, - ChatSettingContextProvider -} from '@/web/core/chat/context/chatSettingContext'; +import { ChatPageContext, ChatPageContextProvider } from '@/web/core/chat/context/chatPageContext'; import ChatTeamApp from '@/pageComponents/chat/ChatTeamApp'; import ChatFavouriteApp from '@/pageComponents/chat/ChatFavouriteApp'; import { useUserStore } from '@/web/support/user/useUserStore'; @@ -32,15 +27,8 @@ 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'; -import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; -const Chat = ({ - myApps, - refreshRecentlyUsed -}: { - myApps: GetRecentlyUsedAppsResponseType; - refreshRecentlyUsed: () => void; -}) => { +const Chat = () => { const { isPc } = useSystem(); const { appId } = useChatStore(); @@ -48,8 +36,8 @@ const Chat = ({ const datasetCiteData = useContextSelector(ChatItemContext, (v) => v.datasetCiteData); const setCiteModalData = useContextSelector(ChatItemContext, (v) => v.setCiteModalData); - const collapse = useContextSelector(ChatSettingContext, (v) => v.collapse); - const pane = useContextSelector(ChatSettingContext, (v) => v.pane); + const collapse = useContextSelector(ChatPageContext, (v) => v.collapse); + const pane = useContextSelector(ChatPageContext, (v) => v.pane); return ( @@ -62,16 +50,14 @@ const Chat = ({ overflow={'hidden'} transition={'width 0.1s ease-in-out'} > - + )} {(!datasetCiteData || isPc) && ( {/* home chat window */} - {pane === ChatSidebarPaneEnum.HOME && ( - - )} + {pane === ChatSidebarPaneEnum.HOME && } {/* favourite apps */} {pane === ChatSidebarPaneEnum.FAVORITE_APPS && } @@ -80,9 +66,7 @@ const Chat = ({ {pane === ChatSidebarPaneEnum.TEAM_APPS && } {/* recently used apps chat window */} - {pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && ( - - )} + {pane === ChatSidebarPaneEnum.RECENTLY_USED_APPS && } {/* setting */} {pane === ChatSidebarPaneEnum.SETTING && } @@ -102,19 +86,23 @@ const Chat = ({ ); }; -const Render = (props: { +type ChatPageProps = { appId: string; isStandalone?: string; showRunningStatus: boolean; showCite: boolean; showFullText: boolean; canDownloadSource: boolean; -}) => { +}; + +const ChatContent = (props: ChatPageProps) => { const { appId, isStandalone } = props; const { chatId } = useChatStore(); const { setUserInfo } = useUserStore(); const { feConfigs } = useSystemStore(); - const { isInitedUser, userInfo, myApps, refreshRecentlyUsed } = useChat(appId); + + const isInitedUser = useContextSelector(ChatPageContext, (v) => v.isInitedUser); + const userInfo = useContextSelector(ChatPageContext, (v) => v.userInfo); const chatHistoryProviderParams = useMemo( () => ({ appId, source: ChatSourceEnum.online }), @@ -155,21 +143,27 @@ const Render = (props: { // show main chat interface return ( - - - - - - - - - + + + + + + + + ); +}; + +const Render = (props: ChatPageProps) => { + return ( + + + ); }; diff --git a/projects/app/src/web/core/chat/context/chatSettingContext.tsx b/projects/app/src/web/core/chat/context/chatPageContext.tsx similarity index 62% rename from projects/app/src/web/core/chat/context/chatSettingContext.tsx rename to projects/app/src/web/core/chat/context/chatPageContext.tsx index 4f2ea4365..e7255a9b0 100644 --- a/projects/app/src/web/core/chat/context/chatSettingContext.tsx +++ b/projects/app/src/web/core/chat/context/chatPageContext.tsx @@ -13,8 +13,14 @@ 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 { useUserStore } from '@/web/support/user/useUserStore'; +import { useMount } from 'ahooks'; +import type { GetRecentlyUsedAppsResponseType } from '@fastgpt/service/core/app/record/type'; +import type { UserType } from '@fastgpt/global/support/user/type'; -export type ChatSettingContextValue = { +export type ChatPageContextValue = { + // Pane & collapse pane: ChatSidebarPaneEnum; handlePaneChange: ( pane: ChatSidebarPaneEnum, @@ -23,12 +29,18 @@ export type ChatSettingContextValue = { ) => void; collapse: CollapseStatusType; onTriggerCollapse: () => void; + // Chat settings chatSettings: ChatSettingType | undefined; refreshChatSetting: () => Promise; logos: { wideLogoUrl?: string; squareLogoUrl?: string }; + // User & apps + isInitedUser: boolean; + userInfo: UserType | null; + myApps: GetRecentlyUsedAppsResponseType; + refreshRecentlyUsed: () => void; }; -export const ChatSettingContext = createContext({ +export const ChatPageContext = createContext({ pane: ChatSidebarPaneEnum.HOME, handlePaneChange: () => {}, collapse: defaultCollapseStatus, @@ -37,19 +49,62 @@ export const ChatSettingContext = createContext({ logos: { wideLogoUrl: '', squareLogoUrl: '' }, refreshChatSetting: function (): Promise { throw new Error('Function not implemented.'); - } + }, + isInitedUser: false, + userInfo: null, + myApps: [], + refreshRecentlyUsed: () => {} }); -export const ChatSettingContextProvider = ({ children }: { children: React.ReactNode }) => { +export const ChatPageContextProvider = ({ + appId: routeAppId, + children +}: { + appId: string; + children: React.ReactNode; +}) => { const router = useRouter(); const { feConfigs } = useSystemStore(); - const { appId, setLastPane, setLastChatAppId, lastPane } = useChatStore(); + const { setSource, setAppId, setLastPane, setLastChatAppId, lastPane } = useChatStore(); + const { userInfo, initUserInfo } = useUserStore(); const { pane = lastPane || ChatSidebarPaneEnum.HOME } = router.query as { pane: ChatSidebarPaneEnum; }; const [collapse, setCollapse] = useState(defaultCollapseStatus); + const [isInitedUser, setIsInitedUser] = useState(false); + + // Get recently used apps + const { data: myApps = [], refresh: refreshRecentlyUsed } = useRequest2( + () => getRecentlyUsedApps(), + { + manual: false, + errorToast: '', + refreshDeps: [userInfo], + pollingInterval: 30000 + } + ); + + // Initialize user info + useMount(async () => { + if (routeAppId) setAppId(routeAppId); + try { + await initUserInfo(); + } catch (error) { + console.log('User not logged in:', error); + } finally { + setSource('online'); + setIsInitedUser(true); + } + }); + + // Sync appId to store as route/appId changes + useEffect(() => { + if (routeAppId) { + setAppId(routeAppId); + } + }, [routeAppId, setAppId, userInfo]); const { data: chatSettings, runAsync: refreshChatSetting } = useRequest2( async () => { @@ -69,8 +124,8 @@ export const ChatSettingContextProvider = ({ children }: { children: React.React if ( pane === ChatSidebarPaneEnum.HOME && - appId !== data.appId && - data.quickAppList.every((q) => q._id !== appId) + routeAppId !== data.appId && + data.quickAppList.every((q) => q._id !== routeAppId) ) { handlePaneChange(ChatSidebarPaneEnum.HOME, data.appId); } @@ -126,7 +181,7 @@ export const ChatSettingContextProvider = ({ children }: { children: React.React setCollapse(collapse === 0 ? 1 : 0); }, [collapse]); - const value: ChatSettingContextValue = useMemoEnhance( + const value: ChatPageContextValue = useMemoEnhance( () => ({ pane, handlePaneChange, @@ -134,10 +189,26 @@ export const ChatSettingContextProvider = ({ children }: { children: React.React onTriggerCollapse, chatSettings, refreshChatSetting, - logos + logos, + isInitedUser, + userInfo, + myApps, + refreshRecentlyUsed }), - [pane, handlePaneChange, collapse, chatSettings, refreshChatSetting, onTriggerCollapse, logos] + [ + pane, + handlePaneChange, + collapse, + onTriggerCollapse, + chatSettings, + refreshChatSetting, + logos, + isInitedUser, + userInfo, + myApps, + refreshRecentlyUsed + ] ); - return {children}; + return {children}; };