feat: implement QuestionBank CRUD with pagination and template query

- Add pagination support to findAll (page, limit query params)
- Add findByTemplateId method to service
- Add GET /by-template/:templateId endpoint to controller
- Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
Developer
2026-04-23 17:19:11 +08:00
commit 0a9588abb7
492 changed files with 112453 additions and 0 deletions
@@ -0,0 +1,162 @@
import { ChatOpenAI } from '@langchain/openai';
import { SystemMessage, HumanMessage } from '@langchain/core/messages';
import { RunnableConfig } from '@langchain/core/runnables';
import { EvaluationState } from '../state';
/**
* Node responsible for generating the final mastery report at the end of the session.
*/
export const reportAnalyzerNode = async (
state: EvaluationState,
config?: RunnableConfig,
): Promise<Partial<EvaluationState>> => {
const { model } = (config?.configurable as any) || {};
const { scores, messages } = state;
const questionList = state.questions || [];
console.log('[AnalyzerNode] Entering node...', {
numScores: Object.keys(scores || {}).length,
numMessages: messages?.length,
scores,
});
if (!model) {
throw new Error('Missing model in node configuration');
}
const scoreSummary = Object.entries(scores)
.map(([qId, score]) => {
const displayId = isNaN(parseInt(qId))
? qId
: (parseInt(qId) + 1).toString();
return `Question ${displayId}: Score ${score}/10`;
})
.join('\n');
const dimensionSummary = questionList.reduce((acc: Record<string, number[]>, q: any) => {
const dim = q.dimension || 'workCapability';
const score = scores[q.id] || 0;
if (!acc[dim]) acc[dim] = [];
acc[dim].push(score);
return acc;
}, {});
const dimensionAvg = Object.entries(dimensionSummary).map(([dim, arr]: [string, any]) => {
const avg = arr.reduce((a: number, b: number) => a + b, 0) / arr.length;
return `${dim}: ${avg.toFixed(1)}/10`;
}).join('\n');
const isZh = state.language === 'zh';
const isJa = state.language === 'ja';
const systemPromptZh = `你是一位客观且严谨的高级教育顾问。
请审查以下评估结果,并为员工提供一份严谨的掌握程度报告。
重要提示:
1. **你必须使用以下语言生成报告:中文 (Simplified Chinese)**。
2. **严禁夹杂日文**。即使对话记录中包含日文,报告内容也必须全中文。
3. 报告的第一行必须严格遵守此格式:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
4. 必须保持客观。如果用户没有提供有效的回答或得分为 0,你必须将其识别为 'Novice',并明确指出他们尚未证明其掌握程度。
5. 不要虚构或幻想优点(如"潜力"或"好奇心"),如果用户明确表示"不知道"或未提供实质内容。
6. 专注于对话记录中已证明的事实。
各维度得分:
${dimensionAvg}
问题与得分:
${scoreSummary}
对话记录:
${messages
.filter((m: any) => m._getType() !== 'system')
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
.join('\n')}
报告结构:
1. 总体级别(已在顶部指定)
2. 各维度得分分析(提示词、LLM、IDE、开发范式、工作能力)
3. 薄弱环节识别
4. 针对性改进建议
5. 推荐的学习路径。`;
const systemPromptJa = `あなたは客観的で厳格なシニア教育コンサルタントです。
以下の評価結果をレビューし、従業員に対して厳格な習熟度レポートを提供してください。
重要事項:
1. **レポートは必ず次の言語で生成してください:日本語**。
2. **中国語を混ぜないでください**。会話ログに中国語が含まれていても、レポートの内容はすべて日本語で記述してください。
3. レポートの最初の行は, 必ず次の形式に従ってください:"LEVEL: [Novice/Proficient/Advanced/Expert]"。
4. 客観的であること。ユーザーが有効な回答を提供しなかった場合、またはスコアが 0 の場合、'Novice' と判定し、習熟度が証明されていないことを明示してください。
5. ユーザーが「わからない」と言ったり、内容を提供しなかった場合に、長所(「ポテンシャル」や「好奇心」など)を捏造しないでください。
6. 会話ログで証明された事実に集中してください。
各ディメンションスコア:
${dimensionAvg}
質問とスコア:
${scoreSummary}
会話ログ:
${messages
.filter((m: any) => m._getType() !== 'system')
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
.join('\n')}
レポート構成:
1. 総合レベル(一番上に指定済み)
2. 各ディメンション分析(提示詞、LLM、IDE、開発範式、工作能力)
3. 薄弱环节识别
4. 推奨される学習パス。`;
const systemPromptEn = `You are an objective and critical seniority education consultant.
Review the following assessment results and provide a rigorous mastery report for the employee.
IMPORTANT:
1. **You MUST generate the report strictly in English.**
2. START the report with exactly this format: "LEVEL: [Novice/Proficient/Advanced/Expert]" on the first line.
3. Be OBJECTIVE. If the user provided no valid answers or scores are 0, you MUST identify them as 'Novice' and explicitly state they have NOT demonstrated mastery.
4. DO NOT invent or hallucinate strengths (like 'potential' or 'curiosity') if the user explicitly said "I don't know" or provided no content.
5. Focus on what was PROVEN in the conversation logs.
DIMENSION SCORES:
${dimensionAvg}
QUESTIONS AND SCORES:
${scoreSummary}
CONVERSATION LOGS:
${messages
.filter((m: any) => m._getType() !== 'system')
.map((m: any) => `${m.role || m._getType()}: ${m.content}`)
.join('\n')}
REPORT STRUCTURE:
1. Overall Level (Already specified at top)
2. Dimension Analysis (Prompt, LLM, IDE, DevPattern, WorkCapability)
3. Weak Areas Identification
4. Targeted Learning Recommendations.`;
const systemPrompt = isZh
? systemPromptZh
: isJa
? systemPromptJa
: systemPromptEn;
const humanMsg = isZh
? '生成最终掌握程度报告。'
: isJa
? '最終的な習熟度レポートを生成してください。'
: 'Generate the final mastery report.';
const response = await model.invoke([
new SystemMessage(systemPrompt),
new HumanMessage(humanMsg),
]);
console.log(
'[AnalyzerNode] Report generated successfully. Length:',
response.content?.toString().length,
);
return {
report: response.content as string,
};
};