From 9a3e44c6d665dbe4faa3b8102a49a6f7dce6b3ef Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Mon, 14 Jul 2025 17:19:21 +0800 Subject: [PATCH] feat(server): add generate title cron resolver (#13189) fix AI-350 ## Summary by CodeRabbit * **New Features** * Added a new option to manually trigger the generation of missing session titles via a GraphQL query. * **Improvements** * The process for generating missing session titles now considers all eligible sessions, without limiting the number processed at a time. --- .../__snapshots__/copilot.spec.ts.md | 4 +--- .../__snapshots__/copilot.spec.ts.snap | Bin 2288 -> 2278 bytes .../__tests__/models/copilot-session.spec.ts | 2 +- .../server/src/models/copilot-session.ts | 3 +-- .../server/src/plugins/copilot/cron.ts | 14 +++++++++----- .../server/src/plugins/copilot/resolver.ts | 14 +++++++++++++- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.md b/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.md index a5c2141d7d..da49bbba31 100644 --- a/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.md +++ b/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.md @@ -431,9 +431,7 @@ Generated by [AVA](https://avajs.dev). ], modelCalls: [ { - args: [ - 100, - ], + args: [], }, ], } diff --git a/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.snap b/packages/backend/server/src/__tests__/__snapshots__/copilot.spec.ts.snap index afecb021420f843fbea1bc310cd771349db5bb8f..1383cf492ce037d9940efeeffd50f4a3edd0c777 100644 GIT binary patch literal 2278 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 K2GQ*SHUI$P0dtZ7 diff --git a/packages/backend/server/src/__tests__/models/copilot-session.spec.ts b/packages/backend/server/src/__tests__/models/copilot-session.spec.ts index d63b1e4068..f851657310 100644 --- a/packages/backend/server/src/__tests__/models/copilot-session.spec.ts +++ b/packages/backend/server/src/__tests__/models/copilot-session.spec.ts @@ -1078,7 +1078,7 @@ test('should get sessions for title generation correctly', async t => { }) ); - const result = await copilotSession.toBeGenerateTitle(10); + const result = await copilotSession.toBeGenerateTitle(); t.snapshot( { diff --git a/packages/backend/server/src/models/copilot-session.ts b/packages/backend/server/src/models/copilot-session.ts index 4bb85c48f3..7912e5373d 100644 --- a/packages/backend/server/src/models/copilot-session.ts +++ b/packages/backend/server/src/models/copilot-session.ts @@ -613,7 +613,7 @@ export class CopilotSessionModel extends BaseModel { } @Transactional() - async toBeGenerateTitle(take: number) { + async toBeGenerateTitle() { const sessions = await this.db.aiSession .findMany({ where: { @@ -628,7 +628,6 @@ export class CopilotSessionModel extends BaseModel { // count assistant messages _count: { select: { messages: { where: { role: 'assistant' } } } }, }, - take, orderBy: { updatedAt: 'desc' }, }) .then(s => s.filter(s => s._count.messages > 0)); diff --git a/packages/backend/server/src/plugins/copilot/cron.ts b/packages/backend/server/src/plugins/copilot/cron.ts index de8cd1eec4..686fac1660 100644 --- a/packages/backend/server/src/plugins/copilot/cron.ts +++ b/packages/backend/server/src/plugins/copilot/cron.ts @@ -11,8 +11,6 @@ declare global { } } -const GENERATE_TITLES_BATCH_SIZE = 100; - @Injectable() export class CopilotCronJobs { private readonly logger = new Logger(CopilotCronJobs.name); @@ -37,6 +35,14 @@ export class CopilotCronJobs { ); } + async triggerGenerateMissingTitles() { + await this.jobs.add( + 'copilot.session.generateMissingTitles', + {}, + { jobId: 'trigger-copilot-generate-missing-titles' } + ); + } + @OnJob('copilot.session.cleanupEmptySessions') async cleanupEmptySessions() { const { removed, cleaned } = @@ -51,9 +57,7 @@ export class CopilotCronJobs { @OnJob('copilot.session.generateMissingTitles') async generateMissingTitles() { - const sessions = await this.models.copilotSession.toBeGenerateTitle( - GENERATE_TITLES_BATCH_SIZE - ); + const sessions = await this.models.copilotSession.toBeGenerateTitle(); for (const session of sessions) { await this.jobs.add('copilot.session.generateTitle', { diff --git a/packages/backend/server/src/plugins/copilot/resolver.ts b/packages/backend/server/src/plugins/copilot/resolver.ts index f658254dc7..47aa613f50 100644 --- a/packages/backend/server/src/plugins/copilot/resolver.ts +++ b/packages/backend/server/src/plugins/copilot/resolver.ts @@ -37,6 +37,7 @@ import { Admin } from '../../core/common'; import { AccessController } from '../../core/permission'; import { UserType } from '../../core/user'; import type { ListSessionOptions, UpdateChatSession } from '../../models'; +import { CopilotCronJobs } from './cron'; import { PromptService } from './prompt'; import { PromptMessage, StreamObject } from './providers'; import { ChatSessionService } from './session'; @@ -773,7 +774,18 @@ class CreateCopilotPromptInput { @Admin() @Resolver(() => String) export class PromptsManagementResolver { - constructor(private readonly promptService: PromptService) {} + constructor( + private readonly cron: CopilotCronJobs, + private readonly promptService: PromptService + ) {} + + @Query(() => Boolean, { + description: 'Trigger generate missing titles cron job', + }) + async triggerGenerateTitleCron() { + await this.cron.triggerGenerateMissingTitles(); + return true; + } @Query(() => [CopilotPromptType], { description: 'List all copilot prompts',