/**
* 用户种子问题管理模块 JavaScript
* 提供用户自己的种子问题的查看、编辑、删除功能
*/
(function() {
'use strict';
// 引用全局的翻译函数
// 翻译函数由 i18n-helper.js 提供
// 模块私有状态
let listPaginator = null;
let editingQuestionId = null;
let container = null;
let questionTypes = {}; // 类型/亚类选项 {type: [subtypes]}
// 用户种子问题管理模块
const UserSeedQuestion = {
init: function(containerElement) {
container = containerElement;
this.initPaginators();
this.initEventListeners();
this.loadQuestionTypes();
this.loadMyQuestions();
},
initPaginators: function() {
// 初始化我的问题列表分页器
listPaginator = createPaginator('list', {
initialPage: 1,
initialPageSize: 20,
onPageChange: (page, pageSize) => {
this.loadMyQuestions();
},
onPageSizeChange: (pageSize) => {
this.loadMyQuestions();
}
});
},
initEventListeners: function() {
// 搜索
const searchInput = container.querySelector('#searchInput');
if (searchInput) {
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.loadMyQuestions();
}, 500);
});
}
// 刷新列表
const refreshListBtn = container.querySelector('#refreshListBtn');
if (refreshListBtn) {
refreshListBtn.addEventListener('click', () => {
this.loadMyQuestions();
});
}
// 使用说明按钮
const showUsageBtn = container.querySelector('#showSeedQuestionUsageBtn');
if (showUsageBtn) {
showUsageBtn.addEventListener('click', () => {
this.showSeedQuestionUsage();
});
}
// 添加问题按钮
const addQuestionBtn = container.querySelector('#addQuestionBtn');
if (addQuestionBtn) {
addQuestionBtn.addEventListener('click', () => {
this.showAddModal();
});
}
// 导入CSV按钮
const importCsvBtn = container.querySelector('#importCsvBtn');
if (importCsvBtn) {
importCsvBtn.addEventListener('click', () => {
this.showImportCsvModal();
});
}
// 编辑模态框
this.initEditModal();
// 添加模态框
this.initAddModal();
// 导入CSV模态框
this.initImportCsvModal();
},
loadQuestionTypes: async function() {
try {
const types = await apiGet('/seed-questions/options/types');
questionTypes = types;
this.populateTypeSelects();
} catch (error) {
console.error('加载类型选项失败:', error);
showError(t('error.loadTypeOptionsFailedRetry'));
}
},
populateTypeSelects: function() {
const typeSelects = ['editType', 'addType'];
typeSelects.forEach(selectId => {
const select = container.querySelector('#' + selectId);
if (!select) return;
// 清空现有选项(保留第一个提示选项)
select.innerHTML = '';
// 添加类型选项
Object.keys(questionTypes).sort().forEach(type => {
const option = document.createElement('option');
option.value = type;
option.textContent = type;
select.appendChild(option);
});
});
},
updateSubtypes: function(event) {
const typeSelect = event ? event.target : null;
if (!typeSelect) return;
const isAdd = typeSelect.id === 'addType';
const subtypeSelectId = isAdd ? 'addSubtype' : 'editSubtype';
const subtypeSelect = container.querySelector('#' + subtypeSelectId);
if (!subtypeSelect) return;
// 清空现有选项
subtypeSelect.innerHTML = '';
const selectedType = typeSelect.value;
if (!selectedType || !questionTypes[selectedType]) {
const option = document.createElement('option');
option.value = '';
option.textContent = t('error.selectTypeFirst');
subtypeSelect.appendChild(option);
return;
}
// 添加亚类选项
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = t('error.selectSubtype');
subtypeSelect.appendChild(defaultOption);
questionTypes[selectedType].sort().forEach(subtype => {
const option = document.createElement('option');
option.value = subtype;
option.textContent = subtype;
subtypeSelect.appendChild(option);
});
},
loadMyQuestions: async function() {
if (!listPaginator) return;
const tbody = container.querySelector('#questionsTableBody');
if (!tbody) return;
try {
tbody.innerHTML = '
| 加载中... |
';
const skip = listPaginator.getSkip();
const limit = listPaginator.getLimit();
const search = container.querySelector('#searchInput')?.value.trim() || null;
const params = new URLSearchParams({
skip: skip.toString(),
limit: limit.toString()
});
if (search) {
params.append('search', search);
}
const questions = await apiGet(`/seed-questions/?${params.toString()}`);
if (questions.length === 0) {
tbody.innerHTML = `📝 ${t('common.noData')} |
`;
const paginationContainer = container.querySelector('#listPaginationContainer');
if (paginationContainer) paginationContainer.classList.add('hidden');
return;
}
tbody.innerHTML = questions.map(q => `
| ${q.id} |
${escapeHtml(q.question)} |
${escapeHtml(q.type)} |
${escapeHtml(q.subtype)} |
${escapeHtml(q.species_or_domain || '-')} |
${escapeHtml(q.model || '-')} |
${q.date ? this.formatDate(q.date) : '-'} |
${q.is_verified ? '✓' : '-'} |
${this.formatDateTime(q.created_at)} |
|
`).join('');
// 更新分页信息(需要获取总数)
const total = await this.getMyQuestionsCount(search);
listPaginator.setTotalCount(total);
listPaginator.render();
const paginationContainer = container.querySelector('#listPaginationContainer');
if (paginationContainer) paginationContainer.classList.remove('hidden');
} catch (error) {
console.error('加载问题列表失败:', error);
tbody.innerHTML = '| 加载失败,请刷新重试 |
';
}
},
getMyQuestionsCount: async function(search) {
try {
const result = await apiGet(`/seed-questions/?skip=0&limit=1000${search ? '&search=' + encodeURIComponent(search) : ''}`);
return result.length;
} catch (error) {
return 0;
}
},
editQuestion: async function(id) {
try {
const question = await apiGet(`/seed-questions/${id}`);
editingQuestionId = id;
// 填充表单
const editQuestionId = container.querySelector('#editQuestionId');
const editQuestion = container.querySelector('#editQuestion');
const editType = container.querySelector('#editType');
const editSubtype = container.querySelector('#editSubtype');
const editSpeciesOrDomain = container.querySelector('#editSpeciesOrDomain');
const editModel = container.querySelector('#editModel');
const editDate = container.querySelector('#editDate');
const editIsVerified = container.querySelector('#editIsVerified');
if (editQuestionId) editQuestionId.value = id;
if (editQuestion) editQuestion.value = question.question;
if (editType) editType.value = question.type;
if (editSpeciesOrDomain) editSpeciesOrDomain.value = question.species_or_domain || '';
if (editModel) editModel.value = question.model || '';
if (editDate) editDate.value = question.date ? question.date.split('T')[0] : '';
if (editIsVerified) editIsVerified.checked = question.is_verified;
// 更新亚类选项
this.updateSubtypes({ target: editType });
// 等待亚类选项更新后再设置值
setTimeout(() => {
if (editSubtype) editSubtype.value = question.subtype;
}, 100);
// 显示模态框
const modal = container.querySelector('#editModal');
if (modal) modal.classList.add('active');
} catch (error) {
console.error('加载问题失败:', error);
showError(t('error.loadQuestionFailedRetry'));
}
},
initEditModal: function() {
const modal = container.querySelector('#editModal');
if (!modal) return;
const closeBtn = container.querySelector('#editModalClose');
const cancelBtn = container.querySelector('#editModalCancel');
const submitBtn = container.querySelector('#editModalSubmit');
const editTypeSelect = container.querySelector('#editType');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
modal.classList.remove('active');
editingQuestionId = null;
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
modal.classList.remove('active');
editingQuestionId = null;
});
}
if (editTypeSelect) {
editTypeSelect.addEventListener('change', (e) => {
this.updateSubtypes(e);
});
}
if (submitBtn) {
submitBtn.addEventListener('click', () => {
this.handleEditSubmit();
});
}
},
handleEditSubmit: async function() {
if (!editingQuestionId) return;
const submitBtn = container.querySelector('#editModalSubmit');
const originalText = submitBtn ? submitBtn.textContent : t('common.save');
try {
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = t('common.saving');
}
const formData = {
question: container.querySelector('#editQuestion')?.value.trim() || '',
type: container.querySelector('#editType')?.value || '',
subtype: container.querySelector('#editSubtype')?.value || '',
species_or_domain: container.querySelector('#editSpeciesOrDomain')?.value.trim() || '',
model: container.querySelector('#editModel')?.value.trim() || '',
date: container.querySelector('#editDate')?.value || '',
is_verified: container.querySelector('#editIsVerified')?.checked
};
// 验证必填字段
if (!formData.question || !formData.type || !formData.subtype ||
!formData.species_or_domain || !formData.model || !formData.date) {
showError(t('common.pleaseFillRequiredFields'));
return;
}
await apiPut(`/seed-questions/${editingQuestionId}`, formData);
showSuccess(t('error.updateSuccess'));
const modal = container.querySelector('#editModal');
if (modal) modal.classList.remove('active');
editingQuestionId = null;
// 刷新列表
this.loadMyQuestions();
} catch (error) {
console.error(t('error.updateFailed') + ':', error);
showError(error.message || t('error.updateFailedRetry'));
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
},
deleteQuestion: async function(id) {
if (!confirm(t('common.deleteConfirmMessage'))) {
return;
}
try {
await apiDelete(`/seed-questions/${id}`);
showSuccess(t('error.deleteSuccess'));
this.loadMyQuestions();
} catch (error) {
console.error(t('error.deleteFailed') + ':', error);
showError(error.message || t('error.deleteFailedRetry'));
}
},
showAddModal: function() {
const modal = container.querySelector('#addModal');
if (modal) {
this.resetAddForm();
modal.classList.add('active');
}
},
initAddModal: function() {
const modal = container.querySelector('#addModal');
if (!modal) return;
const closeBtn = container.querySelector('#addModalClose');
const cancelBtn = container.querySelector('#addModalCancel');
const resetBtn = container.querySelector('#addModalReset');
const submitBtn = container.querySelector('#addModalSubmit');
const addTypeSelect = container.querySelector('#addType');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
modal.classList.remove('active');
this.resetAddForm();
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
modal.classList.remove('active');
this.resetAddForm();
});
}
if (resetBtn) {
resetBtn.addEventListener('click', () => {
this.resetAddForm();
});
}
if (addTypeSelect) {
addTypeSelect.addEventListener('change', (e) => {
this.updateSubtypes(e);
});
}
if (submitBtn) {
submitBtn.addEventListener('click', () => {
this.handleAddSubmit();
});
}
},
resetAddForm: function() {
const form = container.querySelector('#addQuestionForm');
if (form) {
form.reset();
}
const addSubtype = container.querySelector('#addSubtype');
if (addSubtype) {
addSubtype.innerHTML = '';
}
},
handleAddSubmit: async function() {
const submitBtn = container.querySelector('#addModalSubmit');
const originalText = submitBtn ? submitBtn.textContent : t('common.submit');
try {
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = t('common.submitting');
}
const formData = {
question: container.querySelector('#addQuestion')?.value.trim() || '',
type: container.querySelector('#addType')?.value || '',
subtype: container.querySelector('#addSubtype')?.value || '',
species_or_domain: container.querySelector('#addSpeciesOrDomain')?.value.trim() || '',
model: container.querySelector('#addModel')?.value.trim() || '',
date: container.querySelector('#addDate')?.value || '',
is_verified: container.querySelector('#addIsVerified')?.checked
};
// 验证必填字段
if (!formData.question || !formData.type || !formData.subtype ||
!formData.species_or_domain || !formData.model || !formData.date) {
showError(t('common.pleaseFillRequiredFields'));
return;
}
await apiPost('/seed-questions/', formData);
showSuccess(t('error.submitSuccess'));
const modal = container.querySelector('#addModal');
if (modal) modal.classList.remove('active');
this.resetAddForm();
// 刷新列表
this.loadMyQuestions();
} catch (error) {
console.error(t('error.submitFailed') + ':', error);
showError(error.message || t('error.submitFailedRetry'));
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
},
formatDate: function(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleDateString('zh-CN');
},
formatDateTime: function(dateString) {
if (!dateString) return '-';
const date = new Date(dateString);
return date.toLocaleString('zh-CN');
},
showImportCsvModal: function() {
const modal = container.querySelector('#importCsvModal');
if (modal) {
this.resetImportCsvForm();
modal.classList.add('active');
}
},
initImportCsvModal: function() {
const modal = container.querySelector('#importCsvModal');
if (!modal) return;
const closeBtn = container.querySelector('#importCsvModalClose');
const cancelBtn = container.querySelector('#importCsvModalCancel');
const submitBtn = container.querySelector('#importCsvModalSubmit');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
modal.classList.remove('active');
this.resetImportCsvForm();
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', () => {
modal.classList.remove('active');
this.resetImportCsvForm();
});
}
if (submitBtn) {
submitBtn.addEventListener('click', () => {
this.handleImportCsvSubmit();
});
}
},
resetImportCsvForm: function() {
const fileInput = container.querySelector('#csvFileInput');
const resultDiv = container.querySelector('#importResult');
if (fileInput) {
fileInput.value = '';
}
if (resultDiv) {
resultDiv.classList.add('hidden');
resultDiv.innerHTML = '';
}
},
handleImportCsvSubmit: async function() {
const fileInput = container.querySelector('#csvFileInput');
const submitBtn = container.querySelector('#importCsvModalSubmit');
const resultDiv = container.querySelector('#importResult');
const modal = container.querySelector('#importCsvModal');
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
showError(t('error.selectCsvFile'));
return;
}
const file = fileInput.files[0];
if (!file.name.endsWith('.csv')) {
showError(t('error.onlyCsvFormat'));
return;
}
const originalText = submitBtn ? submitBtn.textContent : t('error.importRecords');
try {
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = t('error.importing');
}
// 显示结果区域
if (resultDiv) {
resultDiv.classList.remove('hidden');
resultDiv.innerHTML = '' + t('common.loading') + '
';
}
// 上传文件
const result = await apiUploadFile('/seed-questions/import', file);
// 显示导入结果
if (resultDiv) {
const successCount = result.imported_count || 0;
const failedCount = result.failed_count || 0;
const totalRows = result.total_rows || 0;
const errors = result.errors || [];
let resultHtml = `
导入完成
总行数:
${totalRows}
成功导入:
${successCount}
失败:
${failedCount}
`;
if (errors.length > 0) {
resultHtml += `
错误信息:
${errors.map(error => `- ${escapeHtml(error)}
`).join('')}
`;
}
resultHtml += '
';
resultDiv.innerHTML = resultHtml;
// 如果导入成功,延迟关闭模态框并刷新列表
if (successCount > 0) {
setTimeout(() => {
if (modal) modal.classList.remove('active');
this.resetImportCsvForm();
this.loadMyQuestions();
showSuccess(t('error.importSuccess', { count: successCount }));
}, 2000);
} else {
showError(t('error.importFailedNoRecords'));
}
}
} catch (error) {
console.error(t('error.importFailedRetry') + ':', error);
if (resultDiv) {
resultDiv.classList.remove('hidden');
resultDiv.innerHTML = `
导入失败
${escapeHtml(error.message || t('error.importFailedRetry'))}
`;
}
showError(error.message || t('error.importFailedRetry'));
} finally {
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = originalText;
}
}
},
showSeedQuestionUsage: function() {
// 构建种子问题功能说明内容
const usageContent = `
种子问题功能说明
1. 种子问题管理
- 种子问题是用于生成数据集的基础问题,可以创建、编辑和删除自己的种子问题
- 每个用户只能查看和管理自己创建的种子问题
- 支持搜索功能,可以快速查找特定的种子问题
- 支持分页浏览,方便管理大量种子问题
2. 添加种子问题
- 点击"添加问题"按钮可以创建新的种子问题
- 必填字段:种子问题内容、类型、亚类
- 可选字段:物种/领域、使用的模型、日期、是否人工核验
- 种子问题内容应尽量详细和具体,便于后续生成高质量的QA对
- 类型和亚类用于分类管理,请选择合适的分类
3. 编辑种子问题
- 点击"编辑"按钮可以修改已创建的种子问题
- 可以更新问题的所有字段,包括内容、分类、附加信息等
- 编辑后保存即可更新问题信息
4. 批量导入
- 支持从CSV文件批量导入种子问题
- CSV文件必须包含以下列:种子问题、类型、亚类(必填)
- 可选列:物种/领域、模型、日期(YYYYMMDD或YYYY-MM-DD格式)、是否核验(是/否)
- 导入时会显示成功和失败的数量,以及错误信息
- 批量导入适合一次性添加大量种子问题的场景
5. 分类信息
- 类型:种子问题的主要分类,用于大类别管理
- 亚类:在类型下的细分分类,选择类型后才会显示对应的亚类选项
- 类型和亚类的选项由系统预定义,请从下拉列表中选择
6. 附加信息
- 物种/领域:用于标识问题所属的特定领域或物种(如:水稻、玉米、小麦等)
- 使用的模型:记录生成该问题时使用的AI模型(如:GPT-4、Claude等)
- 日期:记录问题的创建或使用日期
- 是否人工核验:标记该问题是否经过人工审核和验证
7. 搜索功能
- 在搜索框中输入关键词可以快速查找种子问题
- 搜索支持实时匹配,输入后会自动过滤结果
- 清空搜索框可以显示所有问题
使用建议
- 问题质量:种子问题应尽量详细和具体,避免过于宽泛或模糊的问题
- 分类准确:正确选择类型和亚类,便于后续的分类管理和检索
- 信息完整:尽量填写附加信息,有助于问题的管理和追溯
- 批量导入:对于大量种子问题,建议使用CSV批量导入功能,提高效率
- 定期整理:定期检查和整理种子问题,删除重复或不再使用的问题
- 核验标记:对于经过人工审核的问题,建议标记"是否人工核验",提高问题质量的可信度
`;
// 显示对话框(隐藏提交按钮,只显示关闭按钮)
openModal('种子问题功能说明', usageContent, null, true);
}
};
// 导出到全局
window.UserSeedQuestion = UserSeedQuestion;
})();