import { defineComponent } from 'vue' import { ref, reactive, computed, onMounted } from 'vue' import { useRoute } from 'vue-router' import { ElMessage } from 'element-plus' import { chunkApi, questionApi, modelApi } from '@/core/api' export default defineComponent({ name: 'QuestionManage', setup() { const route = useRoute() const projectId = computed(() => route.params.id) const loading = ref(false) const isInitialLoad = ref(true) const generating = ref(false) const questions = ref([]) const chunks = ref([]) const availableModels = ref([]) const showGenerateDialog = ref(false) const filterStatus = ref('') const chunkMap = ref({}) const DEFAULT_GENERATE_PROMPT = '你是一名高质量中文问答数据构建助手。请基于给定 chunk 内容生成准确、自然、可用于训练的数据集问答对。问题必须清晰具体,答案必须直接来自内容或基于内容做合理概括,不要编造原文没有的信息,不要输出与目录、导航、页眉页脚、噪声文字相关的问题。' const generateConfig = reactive({ model_id: '', chunk_ids: [], count: 3, dirty_data_filter: true, thinking_mode: true, preset_prompt: DEFAULT_GENERATE_PROMPT }) // Multi-select const selectedQuestions = ref([]) const filteredQuestions = computed(() => { if (!filterStatus.value) return questions.value return questions.value.filter(q => q.source === filterStatus.value) }) const generatedCount = computed(() => questions.value.filter(q => q.source === 'generated').length) const manualCount = computed(() => questions.value.filter(q => q.source === 'manual').length) const failedCount = computed(() => questions.value.filter(q => q.status === 'failed').length) const generateModels = computed(() => { return availableModels.value.filter(model => { const type = normalizeModelType(model.model_type, model.model_name) return type === 'chat' || type === 'vlm' }) }) const isAllSelected = computed(() => filteredQuestions.value.length > 0 && selectedQuestions.value.length === filteredQuestions.value.length) const selectedCount = computed(() => selectedQuestions.value.length) const toggleSelectAll = () => { if (isAllSelected.value) { selectedQuestions.value = [] } else { selectedQuestions.value = filteredQuestions.value.map(q => q.id) } } const toggleSelect = (id) => { const index = selectedQuestions.value.indexOf(id) if (index === -1) { selectedQuestions.value.push(id) } else { selectedQuestions.value.splice(index, 1) } } const isSelected = (id) => selectedQuestions.value.includes(id) const clearSelection = () => { selectedQuestions.value = [] } const batchDelete = async () => { if (selectedQuestions.value.length === 0) return try { for (const id of selectedQuestions.value) { await questionApi.delete(projectId.value, id) } ElMessage.success(`已删除 ${selectedQuestions.value.length} 个问题`) selectedQuestions.value = [] fetchQuestions() } catch (error) { ElMessage.error('删除失败') } } const normalizeModelType = (modelType, modelName = '') => { if (modelType && modelType !== 'chat') { return modelType } const normalizedName = String(modelName).trim().toLowerCase() if (['rerank', 'bce-reranker', 'gte-rerank'].some(keyword => normalizedName.includes(keyword))) return 'rerank' if (['embedding', 'embed', 'text-embedding', 'bge-', 'gte-', 'm3e', 'e5-', 'jina-embeddings'].some(keyword => normalizedName.includes(keyword))) return 'embedding' if (['vl', 'vision', 'visual', 'multimodal', 'qwen-vl', 'gpt-4o'].some(keyword => normalizedName.includes(keyword))) return 'vlm' return 'chat' } const getProviderLabel = (provider) => { const map = { openai: 'OpenAI Compatible', minimax: 'MiniMax', glm: 'GLM', ali: '阿里云百炼' } return map[provider] || provider } const fetchAvailableModels = async () => { try { const res = await modelApi.list() availableModels.value = Array.isArray(res) ? res : (res?.data || []) if (!generateConfig.model_id && generateModels.value.length) { const defaultModel = generateModels.value.find(model => model.is_default === 'true') || generateModels.value[0] generateConfig.model_id = defaultModel?.id || '' } } catch (error) { availableModels.value = [] } } const fetchAllChunks = async () => { const allChunks = [] let page = 1 let total = 0 do { const res = await chunkApi.list(projectId.value, { page, page_size: 100 }) const items = res.items || res.data || [] total = res.total || res.pagination?.total || items.length allChunks.push(...items) page += 1 } while (allChunks.length < total) return allChunks } const fetchQuestions = async () => { const wasInitial = isInitialLoad.value loading.value = true try { const [chunkList, questionRes] = await Promise.all([ fetchAllChunks(), questionApi.list(projectId.value, { page: 1, page_size: 500 }) ]) chunks.value = chunkList chunkMap.value = Object.fromEntries(chunkList.map(chunk => [chunk.id, chunk])) questions.value = questionRes.items || questionRes.data || [] } catch (error) { questions.value = [] } finally { loading.value = false if (wasInitial) { isInitialLoad.value = false } } } const handleGenerate = async () => { if (generateConfig.chunk_ids.length === 0) { ElMessage.warning('请选择文本块') return } if (!generateConfig.model_id) { ElMessage.warning('请选择生成模型') return } generating.value = true try { await questionApi.generate(projectId.value, generateConfig) ElMessage.success('问题生成任务已启动') showGenerateDialog.value = false setTimeout(fetchQuestions, 2000) } catch (error) { ElMessage.error('生成失败') } finally { generating.value = false } } const handleDelete = async (question) => { try { await questionApi.delete(projectId.value, question.id) ElMessage.success('删除成功') fetchQuestions() } catch (error) { ElMessage.error('删除失败') } } const getTypeColor = (type) => { const map = { 'fact': '#22c55e', 'summary': '#818cf8', 'reasoning': '#f59e0b' } return map[type] || '#818cf8' } const getTypeName = (type) => { const map = { 'fact': '事实性', 'summary': '总结性', 'reasoning': '推理性' } return map[type] || type } const getSourceName = (source) => { const map = { 'generated': 'AI生成', 'manual': '手动', 'failed': '失败' } return map[source] || source } onMounted(() => { fetchAvailableModels() fetchQuestions() }) return { route, projectId, loading, isInitialLoad, generating, questions, chunks, availableModels, showGenerateDialog, filterStatus, chunkMap, DEFAULT_GENERATE_PROMPT, generateConfig, selectedQuestions, filteredQuestions, generatedCount, manualCount, failedCount, generateModels, isAllSelected, selectedCount, toggleSelectAll, toggleSelect, isSelected, clearSelection, batchDelete, normalizeModelType, getProviderLabel, fetchAvailableModels, fetchAllChunks, fetchQuestions, handleGenerate, handleDelete, getTypeColor, getTypeName, getSourceName } } })