feat(aihr): close functional checklist gaps

This commit is contained in:
key
2026-08-02 12:15:45 +08:00
parent 699cc08050
commit 6ad8a017a4
80 changed files with 4930 additions and 430 deletions
+39 -4
View File
@@ -20,6 +20,7 @@ export interface BroadcastMessageSummary {
requiredRead: boolean;
targeted: boolean;
targetReason?: string;
visibilityMode: 'ALL' | 'TARGET_ONLY';
}
export interface BroadcastMessage extends BroadcastMessageSummary {
@@ -50,13 +51,32 @@ export interface BroadcastAttachment {
contentType: string;
status: 'READY';
summary?: string;
downloadUrl: string;
viewUrl: string;
downloadUrl?: string;
allowDownload: boolean;
watermarkText?: string;
topicTags: BroadcastTopicTag[];
insightStatus: BroadcastInsightStatus;
defaultPerspectiveCode?: string;
perspectiveLabels: string[];
perspectives: BroadcastPerspective[];
}
export interface BroadcastTopicTag {
name: string;
sourceType: 'AUTO' | 'MANUAL';
evidenceQuote: string;
}
export interface BroadcastAttachmentView {
id: number;
fileName: string;
content: string;
truncated: boolean;
watermarkText: string;
topicTags: BroadcastTopicTag[];
}
export interface BroadcastMessagePage {
total: number;
pageNum: number;
@@ -78,11 +98,19 @@ export const listBroadcastMessages = (pageNum = 1, pageSize = 20, scope: Broadca
url: `/api/aihr/broadcast/messages?pageNum=${pageNum}&pageSize=${pageSize}&scope=${scope}`,
method: 'GET',
timeout: 15000
});
}).then((page) => ({
...page,
rows: (page.rows || []).map((message) => ({
...message,
visibilityMode: message.visibilityMode || 'ALL'
}))
}));
const normalizeBroadcastAttachment = (attachment: BroadcastAttachment): BroadcastAttachment => ({
...attachment,
allowDownload: attachment.allowDownload !== false,
insightStatus: attachment.insightStatus || 'NOT_PROCESSED',
topicTags: Array.isArray(attachment.topicTags) ? attachment.topicTags : [],
perspectiveLabels: Array.isArray(attachment.perspectiveLabels) ? attachment.perspectiveLabels : [],
perspectives: Array.isArray(attachment.perspectives) ? attachment.perspectives : []
});
@@ -94,8 +122,8 @@ export const getBroadcastMessage = async (id: number) => {
timeout: 15000
});
return message.attachment
? { ...message, attachment: normalizeBroadcastAttachment(message.attachment) }
: message;
? { ...message, visibilityMode: message.visibilityMode || 'ALL', attachment: normalizeBroadcastAttachment(message.attachment) }
: { ...message, visibilityMode: message.visibilityMode || 'ALL' };
};
export const markBroadcastMessageRead = (id: number) =>
@@ -105,6 +133,13 @@ export const markBroadcastMessageRead = (id: number) =>
timeout: 15000
});
export const getBroadcastAttachmentView = (id: number) =>
apiRequest<BroadcastAttachmentView>({
url: `/api/aihr/broadcast/attachments/${id}/view`,
method: 'GET',
timeout: 15000
});
const isSafeMessageId = (value: unknown): value is number =>
typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
+34 -1
View File
@@ -10,6 +10,8 @@ type RawExamQuestion = {
score: number;
explanation?: string;
sortOrder?: number;
sourceQuestionCode?: string | null;
sourceContentVersion?: string | null;
};
type RawExamSummary = {
@@ -53,7 +55,9 @@ const normalizeDetail = (item: RawExamDetail): ExamDetail => ({
options: question.options || [],
score: question.score,
answer: question.correctAnswers || [],
explanation: question.explanation || ''
explanation: question.explanation || '',
sourceQuestionCode: question.sourceQuestionCode || null,
sourceContentVersion: question.sourceContentVersion || null
})),
targets: item.targets || []
});
@@ -64,6 +68,7 @@ const serializeDraft = (draft: SupervisorExamDraft) => ({
positionCode: draft.positionCode,
passScore: draft.passScore,
dueDate: draft.dueDate,
questionDrawRequestId: draft.questionDrawRequestId || null,
targets: draft.targets,
questions: draft.questions.map((question, index) => ({
id: question.id > 0 && question.id < 1_000_000_000 ? question.id : null,
@@ -181,3 +186,31 @@ export const generateSupervisorExamQuestions = async (data: {
}))
};
};
export const drawSupervisorExamQuestions = async (data: {
requestId: string;
positionCode: string;
count: number;
formats: string[];
}): Promise<{ requestId: string; questions: ExamQuestion[] }> => {
const response = await apiRequest<{ requestId: string; questions: RawExamQuestion[] }>({
url: '/api/aihr/mobile/exams/supervisor/question-bank/draw',
method: 'POST',
data,
timeout: 30000
});
return {
requestId: response.requestId,
questions: (response.questions || []).map((question, index) => ({
id: Date.now() + index,
format: question.questionType,
stem: question.stem,
options: question.options || [],
score: question.score,
answer: question.questionType === 'multiple' ? (question.correctAnswers || []) : (question.correctAnswers?.[0] || ''),
explanation: question.explanation || '',
sourceQuestionCode: question.sourceQuestionCode || null,
sourceContentVersion: question.sourceContentVersion || null
}))
};
};
+14
View File
@@ -203,6 +203,20 @@ export const completeOnboardTask = (id: number, evidenceRef?: string) =>
timeout: 15000
});
export const reportOnboardProgress = (id: number, data: {
progressPercent?: number;
positionSeconds?: number;
durationSeconds?: number;
materialVersion?: string | null;
completed?: boolean;
}) =>
apiRequest<void>({
url: `/api/aihr/mobile/onboard/tasks/${id}/progress`,
method: 'POST',
data,
timeout: 15000
});
export const getQualification = () =>
apiRequest<QualificationSummary>({
url: '/api/aihr/mobile/qualification',
+11
View File
@@ -109,6 +109,17 @@ export const publishQuestion = (data: { title: string; content: string; category
timeout: 15000
}).then(normalizeQuestion);
export const publishQuestionFromAnswerFeedback = (data: { requestId: string; idempotencyKey: string }) =>
apiRequest<RawQuestion>({
url: '/api/aihr/community/questions/from-answer-feedback',
method: 'POST',
data: {
requestId: data.requestId,
idempotencyKey: data.idempotencyKey
},
timeout: 15000
}).then(normalizeQuestion);
export const answerQuestion = (questionId: number, content: string) =>
apiRequest<RawAnswer>({
url: `/api/aihr/community/questions/${questionId}/answers`,