Files
gongxue-base/apps/server/src/ai-chat/ai-review.service.ts

100 lines
3.0 KiB
TypeScript

import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { AiReview } from './entities/ai-review.entity';
import type {
AiReviewSection,
AiReviewSectionType,
} from './entities/ai-review.entity';
import {
AiReviewStepSubmitResult,
AiReviewSubmitResult,
} from './ai-review.shared';
import { parseSections } from './ai-review.workbook';
import {
submitAll,
submitGroup,
submitSection,
} from './ai-review.submit';
import type { AiReviewSubmitContext } from './ai-review.submit';
/**
* A2UI batch-import review confirmation lifecycle.
*
* Existing previews can be confirmed per section, per group, or all at
* once; each section is imported in its own transaction in dependency
* order: students → rooms → transfers → checkins.
*/
@Injectable()
export class AiReviewService {
constructor(
@InjectRepository(AiReview)
private readonly reviews: Repository<AiReview>,
private readonly dataSource: DataSource,
) {}
private get submitContext(): AiReviewSubmitContext {
return { reviews: this.reviews, dataSource: this.dataSource };
}
async findOwnedPending(reviewId: string, userId: number): Promise<AiReview> {
const review = await this.reviews.findOne({
where: { id: reviewId, userId, status: 'pending' },
});
if (!review) throw new NotFoundException('导入预览不存在、已确认或已失效');
return review;
}
/**
* Return a review owned by the user regardless of overall status.
* Used by step confirmation so an already-completed card can respond
* with a conflict instead of a plain not-found error.
*/
async findOwned(reviewId: string, userId: number): Promise<AiReview> {
const review = await this.reviews.findOne({
where: { id: reviewId, userId },
});
if (!review) throw new NotFoundException('导入预览不存在');
return review;
}
/** Public shape used by message metadata and the unified `ui.artifact` payload. */
serialize(review: AiReview): Record<string, unknown> {
return {
id: review.id,
title: review.title,
summary: review.summary,
sections: this.parseSections(review.sectionsJson),
status: review.status,
resultSummary: review.resultSummary,
};
}
parseSections(sectionsJson: string): AiReviewSection[] {
return parseSections(sectionsJson);
}
async submitSection(
reviewId: string,
userId: number,
sectionKey: string,
): Promise<AiReviewStepSubmitResult> {
return submitSection(this.submitContext, reviewId, userId, sectionKey);
}
async submitAll(reviewId: string, userId: number): Promise<{
review: AiReview;
result: AiReviewSubmitResult;
}> {
return submitAll(this.submitContext, reviewId, userId);
}
async submitGroup(
reviewId: string,
userId: number,
type: AiReviewSectionType,
): Promise<{ review: AiReview }> {
return submitGroup(this.submitContext, reviewId, userId, type);
}
}