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
+347
View File
@@ -0,0 +1,347 @@
import { Injectable } from '@nestjs/common';
import { errorMessages, logMessages, statusMessages } from './messages';
import { i18nStore } from './i18n.store';
import { DEFAULT_LANGUAGE } from '../common/constants'; // 使用常量定义的默认语言
@Injectable()
export class I18nService {
private readonly defaultLanguage = DEFAULT_LANGUAGE; // 使用常量定义的默认语言
public normalizeLanguage(lang?: string): string {
let language = lang;
if (!language) {
const store = i18nStore.getStore();
language = store?.language;
}
if (!language) return this.defaultLanguage;
// Normalize language codes (e.g., zh-CN -> zh, en-US -> en)
const normalized = language.split('-')[0].toLowerCase();
return normalized;
}
getErrorMessage(key: string, language?: string): string {
const lang = this.normalizeLanguage(language);
return (
errorMessages[lang]?.[key] ||
errorMessages[this.defaultLanguage][key] ||
key
);
}
getLogMessage(key: string, language?: string): string {
const lang = this.normalizeLanguage(language);
return (
logMessages[lang]?.[key] || logMessages[this.defaultLanguage][key] || key
);
}
getStatusMessage(key: string, language?: string): string {
const lang = this.normalizeLanguage(language);
return (
statusMessages[lang]?.[key] ||
statusMessages[this.defaultLanguage][key] ||
key
);
}
// 汎用メッセージ取得メソッド、順次検索
getMessage(key: string, language?: string): string {
const lang = this.normalizeLanguage(language);
// ステータスメッセージ、エラーメッセージ、ログメッセージの順に検索
return (
statusMessages[lang]?.[key] ||
statusMessages[this.defaultLanguage][key] ||
errorMessages[lang]?.[key] ||
errorMessages[this.defaultLanguage][key] ||
logMessages[lang]?.[key] ||
logMessages[this.defaultLanguage][key] ||
key
);
}
// メッセージの取得とフォーマット
formatMessage(
key: string,
args: Record<string, any>,
language?: string,
): string {
let message = this.getMessage(key, language);
for (const [argKey, argValue] of Object.entries(args)) {
message = message.replace(
new RegExp(`\\{${argKey}\\}`, 'g'),
String(argValue),
);
}
return message;
}
// サポートされている言語リストを取得
getSupportedLanguages(): string[] {
return Object.keys(errorMessages);
}
// 言語がサポートされているか確認
isLanguageSupported(language: string): boolean {
return this.getSupportedLanguages().includes(language);
}
// システムプロンプトを取得
getPrompt(
lang: string = this.defaultLanguage,
type: 'withContext' | 'withoutContext' = 'withContext',
hasKnowledgeGroup: boolean = false,
): string {
const language = this.normalizeLanguage(lang);
const noMatchMsg =
statusMessages[language]?.noMatchInKnowledgeGroup ||
statusMessages[this.defaultLanguage].noMatchInKnowledgeGroup;
if (language === 'zh') {
return type === 'withContext'
? `
基于以下知识库内容回答用户问题。
${
hasKnowledgeGroup
? `
**重要提示**: 用户已选择特定知识组,请严格基于以下知识库内容回答。如果知识库中没有相关信息,请明确告知用户:"${noMatchMsg}",然后再提供答案。
`
: ''
}
知识库内容:
{context}
历史对话:
{history}
用户问题:{question}
请用Chinese回答,并严格遵循以下 Markdown 格式要求:
1. **段落与结构**
- 使用清晰的段落分隔,每个要点之间空一行
- 使用标题(## 或 ###)组织长回答
2. **文本格式**
- 使用 **粗体** 强调重要概念和关键词
- 使用列表(- 或 1.)组织多个要点
- 使用 \`代码\` 标记技术术语、命令、文件名
3. **代码展示**
- 使用代码块展示代码,并指定语言:
\`\`\`python
def example():
return "示例"
\`\`\`
- 支持语言:python, javascript, typescript, java, bash, sql 等
4. **图表与可视化**
- 使用 Mermaid 语法绘制流程图、序列图等:
\`\`\`mermaid
graph LR
A[开始] --> B[处理]
B --> C[结束]
\`\`\`
- 适用场景:流程、架构、状态机、时序图
5. **其他要求**
- 回答精炼准确
- 多步骤操作使用有序列表
- 对比类信息建议用表格展示(如果适用)
`
: `
作为智能助手,请回答用户的问题。
历史对话:
{history}
用户问题:{question}
请用Chinese回答。
`;
} else if (language === 'ja') {
return type === 'withContext'
? `
以下のナレッジベースの内容に基づいて、ユーザーの質問に答えてください。
${
hasKnowledgeGroup
? `
**重要**: ユーザーが特定のナレッジグループを選択しました。以下のナレッジベースの内容に厳密に基づいて回答してください。関連情報がナレッジベースに見つからない場合は、回答を提供する前に、ユーザーに明示的に「${noMatchMsg}」と伝えてください。
`
: ''
}
ナレッジベースの内容:
{context}
会話履歴:
{history}
ユーザーの質問:{question}
日本語で回答し、以下のMarkdown形式のガイドラインに厳密に従ってください。
1. **段落と構造**:
- 明確な段落区切りを使用し、要点の間に空行を入れます
- 見出し(## または ###)を使用して長い回答を整理します
2. **テキスト形式**:
- **太字**を使用して重要な概念やキーワードを強調します
- リスト(- または 1.)を使用して複数のポイントを整理します
- \`コード\`を使用して技術用語、コマンド、ファイル名をマークします
3. **コード表示**:
- 言語指定のあるコードブロックを使用します:
\`\`\`python
def example():
return "示例"
\`\`\`
- サポートされている言語:python, javascript, typescript, java, bash, sqlなど
4. **図とチャート**:
- フローチャート、シーケンス図などにMermaid構文を使用します:
\`\`\`mermaid
graph LR
A[開始] --> B[処理]
B --> C[終了]
\`\`\`
- 使用例:プロセスフロー、アーキテクチャ図、状態遷移図、シーケンス図
5. **その他の要件**:
- 回答は簡潔かつ明確にします
- マルチステップ プロセスには番号付きリストを使用します
- 比較情報には表を使用します(該当する場合)
`
: `
インテリジェントなアシスタントとして、ユーザーの質問に答えてください。
会話履歴:
{history}
ユーザーの質問:{question}
日本語で回答してください。
`;
} else {
// Fallback to English for any other language
return type === 'withContext'
? `
Answer the user's question based on the following knowledge base content.
${
hasKnowledgeGroup
? `
**IMPORTANT**: The user has selected a specific knowledge group. Please answer strictly based on the knowledge base content below. If the relevant information is not found in the knowledge base, explicitly tell the user: "${noMatchMsg}", before providing an answer.
`
: ''
}
Knowledge Base CONTENT:
{context}
Conversation history:
{history}
User question: {question}
Please answer in English and strictly follow these Markdown formatting guidelines:
1. **Paragraphs & Structure**:
- Use clear paragraph breaks with blank lines between key points
- Use headings (## or ###) to organize longer answers
2. **Text Formatting**:
- Use **bold** to emphasize important concepts and keywords
- Use lists (- or 1.) to organize multiple points
- Use \`code\` to mark technical terms, commands, file names
3. **Code Display**:
- Use code blocks with language specification:
\`\`\`python
def example():
return "example"
\`\`\`
- Supported languages: python, javascript, typescript, java, bash, sql, etc.
4. **Diagrams & Charts**:
- Use Mermaid syntax for flowcharts, sequence diagrams, etc.:
\`\`\`mermaid
graph LR
A[Start] --> B[Process]
B --> C[End]
\`\`\`
- Use cases: process flows, architecture diagrams, state diagrams, sequence diagrams
5. **Other Requirements**:
- Keep answers concise and clear
- Use numbered lists for multi-step processes
- Use tables for comparison information (if applicable)
`
: `
As an intelligent assistant, please answer the user's question.
Conversation history:
{history}
User question: {question}
Please answer in English.
`;
}
}
// タイトル生成用のプロンプトを取得
getDocumentTitlePrompt(
lang: string = this.defaultLanguage,
contentSample: string,
): string {
const language = this.normalizeLanguage(lang);
if (language === 'zh') {
return `你是一个文档分析师。请阅读以下文本(文档开头部分),并生成一个简炼、专业的标题(不超过50个字符)。
只返回标题文本。不要包含任何解释性文字或前导词(如“标题是:”)。
语言:Chinese
文本内容:
${contentSample}`;
} else if (language === 'ja') {
return `あなたは文書分析の専門家です。以下のテキスト(文書の冒頭部分)を読み、簡潔で専門的なタイトル(50文字以内)を生成してください。
タイトルのみを返してください。前置きや説明は不要です。
言語:Japanese
テキスト:
${contentSample}`;
} else {
return `You are a document analyzer. Read the following text (start of a document) and generate a concise, professional title (max 50 chars).
Return ONLY the title text. No preamble like "The title is...".
Language: English
Text:
${contentSample}`;
}
}
getChatTitlePrompt(
lang: string = this.defaultLanguage,
userMessage: string,
aiResponse: string,
): string {
const language = this.normalizeLanguage(lang);
if (language === 'zh') {
return `根据以下对话片段,生成一个简短、描述性的标题(不超过50个字符),总结讨论的主题。
只返回标题文本。不要包含任何前导词。
语言:Chinese
片段:
用户: ${userMessage}
助手: ${aiResponse}`;
} else if (language === 'ja') {
return `以下の会話のスニペットに基づいて、話題を要約する短く説明的なタイトル(50文字以内)を生成してください。
タイトルのみを返してください。前置きは不要です。
言語:Japanese
スニペット:
ユーザー: ${userMessage}
アシスタント: ${aiResponse}`;
} else {
return `Based on the following conversation snippet, generate a short, descriptive title (max 50 chars) that summarizes the topic.
Return ONLY the title. No preamble.
Language: English
Snippet:
User: ${userMessage}
Assistant: ${aiResponse}`;
}
}
}