Files
AFFiNE-Mirror/packages/backend/server/src/plugins/copilot/cron.ts
T
DarkSky 9677bdf50d feat(server): skip cleanup for stale workspace (#13418)
fix AI-408

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a new field to workspaces to track the last time embeddings were
checked.
* Cleanup jobs for workspace embeddings now skip workspaces that haven't
changed in over 30 days or have no embeddings, improving efficiency.
* Cleanup jobs are now automatically triggered when a workspace is
updated.

* **Improvements**
* Enhanced workspace selection for cleanup and indexing tasks to use
more precise filters and batching.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-08-06 08:11:50 +00:00

117 lines
3.3 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { JOB_SIGNAL, JobQueue, OneDay, OnJob } from '../../base';
import { Models } from '../../models';
const CLEANUP_EMBEDDING_JOB_BATCH_SIZE = 100;
declare global {
interface Jobs {
'copilot.session.cleanupEmptySessions': {};
'copilot.session.generateMissingTitles': {};
'copilot.workspace.cleanupTrashedDocEmbeddings': {
nextSid?: number;
};
}
}
@Injectable()
export class CopilotCronJobs {
private readonly logger = new Logger(CopilotCronJobs.name);
constructor(
private readonly models: Models,
private readonly jobs: JobQueue
) {}
async triggerCleanupTrashedDocEmbeddings() {
await this.jobs.add(
'copilot.workspace.cleanupTrashedDocEmbeddings',
{},
{ jobId: 'daily-copilot-cleanup-trashed-doc-embeddings' }
);
}
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
async dailyCleanupJob() {
await this.jobs.add(
'copilot.session.cleanupEmptySessions',
{},
{ jobId: 'daily-copilot-cleanup-empty-sessions' }
);
await this.jobs.add(
'copilot.session.generateMissingTitles',
{},
{ jobId: 'daily-copilot-generate-missing-titles' }
);
await this.jobs.add(
'copilot.workspace.cleanupTrashedDocEmbeddings',
{},
{ jobId: 'daily-copilot-cleanup-trashed-doc-embeddings' }
);
}
async triggerGenerateMissingTitles() {
await this.jobs.add(
'copilot.session.generateMissingTitles',
{},
{ jobId: 'trigger-copilot-generate-missing-titles' }
);
}
@OnJob('copilot.session.cleanupEmptySessions')
async cleanupEmptySessions() {
const { removed, cleaned } =
await this.models.copilotSession.cleanupEmptySessions(
new Date(Date.now() - OneDay)
);
this.logger.log(
`Cleanup completed: ${removed} sessions deleted, ${cleaned} sessions marked as deleted`
);
}
@OnJob('copilot.session.generateMissingTitles')
async generateMissingTitles() {
const sessions = await this.models.copilotSession.toBeGenerateTitle();
for (const session of sessions) {
await this.jobs.add('copilot.session.generateTitle', {
sessionId: session.id,
});
}
this.logger.log(
`Scheduled title generation for ${sessions.length} sessions`
);
}
@OnJob('copilot.workspace.cleanupTrashedDocEmbeddings')
async cleanupTrashedDocEmbeddings(
params: Jobs['copilot.workspace.cleanupTrashedDocEmbeddings']
) {
const nextSid = params.nextSid ?? 0;
// only consider workspaces that cleared their embeddings more than 24 hours ago
const oneDayAgo = new Date(Date.now() - OneDay);
const workspaces = await this.models.workspace.list(
{ sid: { gt: nextSid }, lastCheckEmbeddings: { lt: oneDayAgo } },
{ id: true, sid: true },
CLEANUP_EMBEDDING_JOB_BATCH_SIZE
);
if (!workspaces.length) {
return JOB_SIGNAL.Done;
}
for (const { id: workspaceId } of workspaces) {
await this.jobs.add(
'copilot.embedding.cleanupTrashedDocEmbeddings',
{ workspaceId },
{ jobId: `cleanup-trashed-doc-embeddings-${workspaceId}` }
);
}
params.nextSid = workspaces[workspaces.length - 1].sid;
return JOB_SIGNAL.Repeat;
}
}