feat(server): add generate title cron resolver (#13189)

fix AI-350

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-07-14 17:19:21 +08:00
committed by GitHub
parent 7b53641a94
commit 9a3e44c6d6
6 changed files with 25 additions and 12 deletions
@@ -431,9 +431,7 @@ Generated by [AVA](https://avajs.dev).
], ],
modelCalls: [ modelCalls: [
{ {
args: [ args: [],
100,
],
}, },
], ],
} }
@@ -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( t.snapshot(
{ {
@@ -613,7 +613,7 @@ export class CopilotSessionModel extends BaseModel {
} }
@Transactional() @Transactional()
async toBeGenerateTitle(take: number) { async toBeGenerateTitle() {
const sessions = await this.db.aiSession const sessions = await this.db.aiSession
.findMany({ .findMany({
where: { where: {
@@ -628,7 +628,6 @@ export class CopilotSessionModel extends BaseModel {
// count assistant messages // count assistant messages
_count: { select: { messages: { where: { role: 'assistant' } } } }, _count: { select: { messages: { where: { role: 'assistant' } } } },
}, },
take,
orderBy: { updatedAt: 'desc' }, orderBy: { updatedAt: 'desc' },
}) })
.then(s => s.filter(s => s._count.messages > 0)); .then(s => s.filter(s => s._count.messages > 0));
@@ -11,8 +11,6 @@ declare global {
} }
} }
const GENERATE_TITLES_BATCH_SIZE = 100;
@Injectable() @Injectable()
export class CopilotCronJobs { export class CopilotCronJobs {
private readonly logger = new Logger(CopilotCronJobs.name); 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') @OnJob('copilot.session.cleanupEmptySessions')
async cleanupEmptySessions() { async cleanupEmptySessions() {
const { removed, cleaned } = const { removed, cleaned } =
@@ -51,9 +57,7 @@ export class CopilotCronJobs {
@OnJob('copilot.session.generateMissingTitles') @OnJob('copilot.session.generateMissingTitles')
async generateMissingTitles() { async generateMissingTitles() {
const sessions = await this.models.copilotSession.toBeGenerateTitle( const sessions = await this.models.copilotSession.toBeGenerateTitle();
GENERATE_TITLES_BATCH_SIZE
);
for (const session of sessions) { for (const session of sessions) {
await this.jobs.add('copilot.session.generateTitle', { await this.jobs.add('copilot.session.generateTitle', {
@@ -37,6 +37,7 @@ import { Admin } from '../../core/common';
import { AccessController } from '../../core/permission'; import { AccessController } from '../../core/permission';
import { UserType } from '../../core/user'; import { UserType } from '../../core/user';
import type { ListSessionOptions, UpdateChatSession } from '../../models'; import type { ListSessionOptions, UpdateChatSession } from '../../models';
import { CopilotCronJobs } from './cron';
import { PromptService } from './prompt'; import { PromptService } from './prompt';
import { PromptMessage, StreamObject } from './providers'; import { PromptMessage, StreamObject } from './providers';
import { ChatSessionService } from './session'; import { ChatSessionService } from './session';
@@ -773,7 +774,18 @@ class CreateCopilotPromptInput {
@Admin() @Admin()
@Resolver(() => String) @Resolver(() => String)
export class PromptsManagementResolver { 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], { @Query(() => [CopilotPromptType], {
description: 'List all copilot prompts', description: 'List all copilot prompts',