feat: improve report and comment workflows
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import 'reflect-metadata'
|
||||
|
||||
import { randomUUID } from 'crypto'
|
||||
import { Brackets, type Repository } from 'typeorm'
|
||||
import { BrowserWindow } from 'electron'
|
||||
|
||||
import {
|
||||
StudentProfileEntitySchema,
|
||||
@@ -10,6 +12,8 @@ import { ClassEntitySchema, type ClassEntity } from '../entities/ClassEntity'
|
||||
import type {
|
||||
ChildProfile,
|
||||
ClassProfile,
|
||||
CommentGenerationProgress,
|
||||
GenerateStudentCommentsInput,
|
||||
ListStudentProfilesInput,
|
||||
LoadStudentDataInput
|
||||
} from '../types/student'
|
||||
@@ -118,11 +122,14 @@ function mapClassEntity(entity: ClassEntity): ClassProfile {
|
||||
name: entity.name,
|
||||
type: normalizeClassType(entity.type),
|
||||
teacherNames: parseTeacherNames(entity.teacherNames || entity.teacherName || ''),
|
||||
familyPhoto: entity.familyPhoto ?? undefined
|
||||
familyPhoto: entity.familyPhoto || undefined
|
||||
}
|
||||
}
|
||||
|
||||
function mapChildEntity(entity: StudentProfileEntity, options: { includeImages?: boolean } = {}): ChildProfile {
|
||||
function mapChildEntity(
|
||||
entity: StudentProfileEntity,
|
||||
options: { includeImages?: boolean } = {}
|
||||
): ChildProfile {
|
||||
return {
|
||||
id: entity.id,
|
||||
classId: entity.classId,
|
||||
@@ -154,7 +161,8 @@ async function mapClassProfile(profile: ClassProfile): Promise<ClassEntity> {
|
||||
type: profile.type,
|
||||
teacherName: (profile.teacherNames ?? []).join(' ') || null,
|
||||
teacherNames: stringifyTeacherNames(profile.teacherNames ?? []),
|
||||
familyPhoto: (await storeImageValue('classes', profile.id, 'familyPhoto', profile.familyPhoto)) || null
|
||||
familyPhoto:
|
||||
(await storeImageValue('classes', profile.id, 'familyPhoto', profile.familyPhoto)) || null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +213,16 @@ function buildStudentCommentPrompt(profile: ChildProfile): string {
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function getStudentDisplayName(profile: ChildProfile | StudentProfileEntity): string {
|
||||
return profile.name || profile.englishName || '未命名学生'
|
||||
}
|
||||
|
||||
function sendCommentProgress(progress: CommentGenerationProgress): void {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
window.webContents.send('comments:generation-progress', progress)
|
||||
}
|
||||
}
|
||||
|
||||
async function getRepositories(): Promise<{
|
||||
classRepository: Repository<ClassEntity>
|
||||
childRepository: Repository<StudentProfileEntity>
|
||||
@@ -268,8 +286,12 @@ export async function loadStudentData(input: LoadStudentDataInput = {}): Promise
|
||||
}
|
||||
}
|
||||
|
||||
const hydratedClasses = includeImages
|
||||
? await Promise.all(normalizedClasses.map(hydrateClassImages))
|
||||
: normalizedClasses
|
||||
|
||||
return {
|
||||
classes: (await Promise.all(normalizedClasses.map(hydrateClassImages))).map(mapClassEntity),
|
||||
classes: hydratedClasses.map(mapClassEntity),
|
||||
profiles: hydratedProfiles.map((profile) => mapChildEntity(profile, { includeImages }))
|
||||
}
|
||||
}
|
||||
@@ -369,15 +391,10 @@ export async function updateStudentProfile(profile: ChildProfile): Promise<Child
|
||||
return mapChildEntity(await hydrateStudentImages(storedProfile))
|
||||
}
|
||||
|
||||
export async function generateStudentComment(profileId: string): Promise<ChildProfile> {
|
||||
const source = await getAppDataSource()
|
||||
const repository = source.getRepository<StudentProfileEntity>(StudentProfileEntitySchema)
|
||||
const entity = await repository.findOneBy({ id: profileId })
|
||||
|
||||
if (!entity) {
|
||||
throw new Error('学生信息不存在')
|
||||
}
|
||||
|
||||
async function generateStudentCommentForEntity(
|
||||
entity: StudentProfileEntity,
|
||||
repository: Repository<StudentProfileEntity>
|
||||
): Promise<ChildProfile> {
|
||||
const profile = mapChildEntity(entity)
|
||||
const settings = await loadSettings()
|
||||
|
||||
@@ -451,6 +468,117 @@ export async function generateStudentComment(profileId: string): Promise<ChildPr
|
||||
return mapChildEntity(await hydrateStudentImages(storedProfile))
|
||||
}
|
||||
|
||||
export async function generateStudentComment(profileId: string): Promise<ChildProfile> {
|
||||
const source = await getAppDataSource()
|
||||
const repository = source.getRepository<StudentProfileEntity>(StudentProfileEntitySchema)
|
||||
const entity = await repository.findOneBy({ id: profileId })
|
||||
|
||||
if (!entity) {
|
||||
throw new Error('学生信息不存在')
|
||||
}
|
||||
|
||||
return generateStudentCommentForEntity(entity, repository)
|
||||
}
|
||||
|
||||
export async function generateStudentComments(input: GenerateStudentCommentsInput): Promise<{
|
||||
profiles: ChildProfile[]
|
||||
skipped: Array<{ studentName: string; reason: string }>
|
||||
}> {
|
||||
const source = await getAppDataSource()
|
||||
const repository = source.getRepository<StudentProfileEntity>(StudentProfileEntitySchema)
|
||||
const selectedIds = new Set(input.profileIds ?? [])
|
||||
const allProfiles = await repository.find({
|
||||
where: input.classId ? { classId: input.classId } : undefined,
|
||||
order: {
|
||||
name: 'ASC',
|
||||
id: 'ASC'
|
||||
}
|
||||
})
|
||||
const targetProfiles =
|
||||
selectedIds.size > 0
|
||||
? allProfiles.filter((profile) => selectedIds.has(profile.id))
|
||||
: allProfiles
|
||||
|
||||
if (targetProfiles.length === 0) {
|
||||
throw new Error('没有可生成评语的学生')
|
||||
}
|
||||
|
||||
const batchId = randomUUID()
|
||||
const className = targetProfiles[0]?.className
|
||||
const profiles: ChildProfile[] = []
|
||||
const skipped: Array<{ studentName: string; reason: string }> = []
|
||||
|
||||
sendCommentProgress({
|
||||
batchId,
|
||||
status: 'started',
|
||||
classId: input.classId,
|
||||
className,
|
||||
current: 0,
|
||||
total: targetProfiles.length
|
||||
})
|
||||
|
||||
for (const [profileIndex, profile] of targetProfiles.entries()) {
|
||||
const studentName = getStudentDisplayName(profile)
|
||||
|
||||
try {
|
||||
sendCommentProgress({
|
||||
batchId,
|
||||
status: 'student-started',
|
||||
classId: profile.classId,
|
||||
className: profile.className,
|
||||
studentId: profile.id,
|
||||
studentName,
|
||||
current: profileIndex + 1,
|
||||
total: targetProfiles.length
|
||||
})
|
||||
|
||||
const nextProfile = await generateStudentCommentForEntity(profile, repository)
|
||||
profiles.push(nextProfile)
|
||||
|
||||
sendCommentProgress({
|
||||
batchId,
|
||||
status: 'student-finished',
|
||||
classId: profile.classId,
|
||||
className: profile.className,
|
||||
studentId: profile.id,
|
||||
studentName,
|
||||
current: profileIndex + 1,
|
||||
total: targetProfiles.length,
|
||||
profile: nextProfile
|
||||
})
|
||||
} catch (error) {
|
||||
const reason = error instanceof Error ? error.message : '生成失败'
|
||||
|
||||
skipped.push({
|
||||
studentName,
|
||||
reason
|
||||
})
|
||||
sendCommentProgress({
|
||||
batchId,
|
||||
status: 'student-failed',
|
||||
classId: profile.classId,
|
||||
className: profile.className,
|
||||
studentId: profile.id,
|
||||
studentName,
|
||||
current: profileIndex + 1,
|
||||
total: targetProfiles.length,
|
||||
error: reason
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sendCommentProgress({
|
||||
batchId,
|
||||
status: 'finished',
|
||||
classId: input.classId,
|
||||
className,
|
||||
current: targetProfiles.length,
|
||||
total: targetProfiles.length
|
||||
})
|
||||
|
||||
return { profiles, skipped }
|
||||
}
|
||||
|
||||
export async function saveClasses(classes: ClassProfile[]): Promise<void> {
|
||||
const source = await getAppDataSource()
|
||||
|
||||
@@ -491,7 +619,9 @@ export async function migrateLocalStudentData(
|
||||
}
|
||||
|
||||
await classRepository.save(await Promise.all(classes.map(mapClassProfile)))
|
||||
await childRepository.save(await Promise.all(profiles.map((profile) => mapChildProfile(profile))))
|
||||
await childRepository.save(
|
||||
await Promise.all(profiles.map((profile) => mapChildProfile(profile)))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -515,7 +645,9 @@ export async function deleteClassWithProfiles(classId: string): Promise<void> {
|
||||
const source = await getAppDataSource()
|
||||
|
||||
await source.transaction(async (manager) => {
|
||||
await manager.getRepository<StudentProfileEntity>(StudentProfileEntitySchema).delete({ classId })
|
||||
await manager
|
||||
.getRepository<StudentProfileEntity>(StudentProfileEntitySchema)
|
||||
.delete({ classId })
|
||||
await manager.getRepository<ClassEntity>(ClassEntitySchema).delete({ id: classId })
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user